{"thread":{"id":"66216","subject":"[PATCH 0/8] odb: write alternates at creation time","startedAt":"2026-08-25T14:12:37Z","lastAt":"2026-09-14T10:22:44Z","messageCount":85,"participants":["Patrick Steinhardt","Toon Claes","Junio C Hamano","Justin Tobler","Karthik Nayak"],"isPatch":true,"patchVersion":1,"patchTotal":8},"messages":[{"id":"551173","messageId":"20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im","threadId":"66216","inReplyTo":null,"subject":"[PATCH 0/8] odb: write alternates at creation time","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:11:49Z","receivedAt":"2026-08-25T14:12:37Z","isPatch":true,"body":"Hi,\n\nwriting alternates into the object database currently happens via\n`odb_source_write_alternate()`. But while that creates the ability to\ncreate alternates at arbitrary points of a source's lifetime, we don't\nuse that functionality in the first place. Instead, we only ever write\nalternates when creating a new repository.\n\nThis design is suboptimal due to a couple of reasons:\n\n  - It requires us to have a `write_alternates()` callback, which is\n    overblown as we never even write alternates to an object database\n    after it has been created.\n\n  - We're about to make alternates an implementation detail of the\n    object database's backend in a future patch series, so alternate\n    implementations may not even support them.\n\n  - The backend has more flexibility with how exactly alternates are\n    configured when it itself is in full control over their setup at the\n    time where it creates the object database itself.\n\nThis patch series thus refactors how we handle alternates so that we\ndon't write them ad-hoc anymore. Instead, the series introduces a new\noption for `odb_source_create_on_disk()` that makes it handle those\nalternates at creation time.\n\nThis is part of the bigger goal of moving handling of alternates into\nthe \"files\" backend.\n\nThis series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)\nwith ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop\n`alternates_db` field, 2026-08-17) merged into it.\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (8):\n      builtin/clone: defer setup of the object database\n      builtin/clone: move around `setup_reference()`\n      builtin/clone: refactor handling of \"--reference{,-if-able}\"\n      builtin/clone: move setup of alternates for shared local clones\n      builtin/clone: move setup of alternates for non-shared local clones\n      odb/source: support writing alternates when creating the database\n      builtin/clone: write alternates via `odb_create_on_disk()`\n      odb/source: remove the ability to write alternates\n\n builtin/clone.c            | 108 ++++++++++++++++++++++++--------------\n odb.c                      |   9 ----\n odb.h                      |   7 ---\n odb/source-files.c         | 128 +++++++++++++++++++++++++--------------------\n odb/source-inmemory.c      |   7 ---\n odb/source-loose.c         |   7 ---\n odb/source-packed.c        |   7 ---\n odb/source.h               |  44 ++++++----------\n setup.c                    |  15 ++++--\n setup.h                    |  15 ++++--\n t/t5604-clone-reference.sh |  25 +++++++++\n 11 files changed, 201 insertions(+), 171 deletions(-)\n\n\n---\nbase-commit: afa255aeb620346d56a2c01fb5ae9163513c56d7\nchange-id: 20260813-pks-odb-write-alternates-at-creation-time-64010deb94a0\n\n"},{"id":"551174","messageId":"20260825-pks-odb-write-alternates-at-creation-time-v1-1-911513ba95c3@pks.im","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im","subject":"[PATCH 1/8] builtin/clone: defer setup of the object database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:11:50Z","receivedAt":"2026-08-25T14:12:38Z","isPatch":true,"body":"When cloning a repository we defer initialization of the reference\ndatabase. This is because we don't yet know all details required for us\nto initialize the refdb in the first place. Most importantly, what we\nare missing is information about the object hash.\n\nWe don't do the same thing for the object database yet, but here we\nessentially have the same problem. While the \"files\" database does not\nneed any information about the object format at creation time, alternate\nbackends are likely to require that information so that they can\nproperly set up their data structures.\n\nBesides this forward-looking future proofing though, we also have a\nsecond use case for deferring initialization of the object database,\nnamely alternates. When initializing the object database we do not yet\nknow whether we'll need alternates or not because this depends on the\nrepository we're about to clone from. If it is a local repository and\nthe user has passed \"--refernce{,-if-able}\", then we will end up writing\nalternates into the object database.\n\nThe ugly part though is that we cannot determine where the repository is\ngetting cloned from before it has been initialized. While we of course\nalready have access to the user-provided URI, that URI can be very well\nrewritten via \"url.<base>.insteadOf\". We can of course read the global-\nand system-level configuration to resolve it. But we explicitly resolve\nthe URI a second time after we have initialized the repository because\nit can happen that we copy a \".git/config\" over from our templates, and\nthat file may cause us to rewrite the path.\n\nIn a subsequent commit though we'll start to write alternates as part of\nthe repository initialization, so we'll need to have the URI properly\nresolved before we can initialize the object database. This is ugly, but\nas mentioned above it makes sense for us to defer its initialization\nanyway so that we also know about the object hash already.\n\nIntroduce a new flag that makes `init_db()` skip initializing the object\ndatabase. Expose `create_object_database()` and make use of it after we\nhave resolved the URI.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 12 ++++++++----\n setup.c         |  8 +++++---\n setup.h         | 15 ++++++++++-----\n 3 files changed, 23 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 5b25cca510..0a67492ebd 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1184,11 +1184,14 @@ int cmd_clone(int argc,\n \t * database. We do not yet know about the object format of the\n \t * repository, and reference backends may persist that information into\n \t * their on-disk data structures.\n+\t *\n+\t * Furthermore, we skip initializing the object database so that we can\n+\t * first resolve potential alternates before creating it.\n \t */\n \tinit_db(the_repository, git_dir, real_git_dir, work_tree, option_template,\n \t\tGIT_HASH_UNKNOWN, ref_storage_format, NULL,\n \t\tdo_not_override_repo_unix_permissions,\n-\t\tINIT_DB_QUIET | INIT_DB_SKIP_REFDB);\n+\t\tINIT_DB_QUIET | INIT_DB_SKIP_REFDB | INIT_DB_SKIP_ODB);\n \n \tif (real_git_dir) {\n \t\tfree((char *)git_dir);\n@@ -1311,9 +1314,6 @@ int cmd_clone(int argc,\n \t\tstrbuf_reset(&key);\n \t}\n \n-\tif (option_required_reference.nr || option_optional_reference.nr)\n-\t\tsetup_reference();\n-\n \tremote = remote_get_early(remote_name);\n \n \tif (!option_rev)\n@@ -1342,6 +1342,10 @@ int cmd_clone(int argc,\n \tif (option_local > 0 && !is_local)\n \t\twarning(_(\"--local is ignored\"));\n \n+\tcreate_object_database(the_repository);\n+\tif (option_required_reference.nr || option_optional_reference.nr)\n+\t\tsetup_reference();\n+\n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\n \ttransport->family = family;\ndiff --git a/setup.c b/setup.c\nindex d90654f584..e654e27d05 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,\n \treturn reinit;\n }\n \n-static void create_object_database(struct repository *repo)\n+void create_object_database(struct repository *repo)\n {\n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\n@@ -2829,7 +2829,8 @@ int init_db(struct repository *repo,\n \t    const char *template_dir, int hash,\n \t    enum ref_storage_format ref_storage_format,\n \t    const char *initial_branch,\n-\t    int init_shared_repository, unsigned int flags)\n+\t    int init_shared_repository,\n+\t    enum init_db_flags flags)\n {\n \tint reinit;\n \tint exist_ok = flags & INIT_DB_EXIST_OK;\n@@ -2903,7 +2904,8 @@ int init_db(struct repository *repo,\n \n \tif (!(flags & INIT_DB_SKIP_REFDB))\n \t\tcreate_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);\n-\tcreate_object_database(repo);\n+\tif (!(flags & INIT_DB_SKIP_ODB))\n+\t\tcreate_object_database(repo);\n \n \tstartup_info->have_repository = 1;\n \ndiff --git a/setup.h b/setup.h\nindex 763fd384e8..570ebcd150 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -256,9 +256,12 @@ int apply_repository_format(struct repository *repo,\n \n const char *get_template_dir(const char *option_template);\n \n-#define INIT_DB_QUIET      (1 << 0)\n-#define INIT_DB_EXIST_OK   (1 << 1)\n-#define INIT_DB_SKIP_REFDB (1 << 2)\n+enum init_db_flags {\n+\tINIT_DB_QUIET = (1 << 0),\n+\tINIT_DB_EXIST_OK = (1 << 1),\n+\tINIT_DB_SKIP_REFDB = (1 << 2),\n+\tINIT_DB_SKIP_ODB = (1 << 3),\n+};\n \n int init_db(struct repository *repo,\n \t    const char *git_dir,\n@@ -266,13 +269,15 @@ int init_db(struct repository *repo,\n \t    const char *worktree,\n \t    const char *template_dir, int hash_algo,\n \t    enum ref_storage_format ref_storage_format,\n-\t    const char *initial_branch, int init_shared_repository,\n-\t    unsigned int flags);\n+\t    const char *initial_branch,\n+\t    int init_shared_repository,\n+\t    enum init_db_flags flags);\n void initialize_repository_version(struct repository *repo,\n \t\t\t\t   int hash_algo,\n \t\t\t\t   enum ref_storage_format ref_storage_format,\n \t\t\t\t   int reinit);\n void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);\n+void create_object_database(struct repository *repo);\n \n /*\n  * NOTE NOTE NOTE!!\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551175","messageId":"20260825-pks-odb-write-alternates-at-creation-time-v1-2-911513ba95c3@pks.im","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im","subject":"[PATCH 2/8] builtin/clone: move around `setup_reference()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:11:51Z","receivedAt":"2026-08-25T14:12:40Z","isPatch":true,"body":"In a subsequent commit, `setup_reference()` will start to call\n`copy_alternates()`. Prepare for this by moving the function further\ndown so that we can avoid adding a declaration.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 0a67492ebd..8c990ce0cc 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)\n \treturn 0;\n }\n \n-static void setup_reference(void)\n-{\n-\tint required = 1;\n-\tfor_each_string_list(&option_required_reference,\n-\t\t\t     add_one_reference, &required);\n-\trequired = 0;\n-\tfor_each_string_list(&option_optional_reference,\n-\t\t\t     add_one_reference, &required);\n-}\n-\n static void copy_alternates(struct strbuf *src, const char *src_repo)\n {\n \t/*\n@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n+static void setup_reference(void)\n+{\n+\tint required = 1;\n+\tfor_each_string_list(&option_required_reference,\n+\t\t\t     add_one_reference, &required);\n+\trequired = 0;\n+\tfor_each_string_list(&option_optional_reference,\n+\t\t\t     add_one_reference, &required);\n+}\n+\n static void mkdir_if_missing(const char *pathname, mode_t mode)\n {\n \tstruct stat st;\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551176","messageId":"20260825-pks-odb-write-alternates-at-creation-time-v1-3-911513ba95c3@pks.im","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im","subject":"[PATCH 3/8] builtin/clone: refactor handling of \"--reference{,-if-able}\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:11:52Z","receivedAt":"2026-08-25T14:12:42Z","isPatch":true,"body":"Users can pass \"--reference{,-if-able}\" to git-clone(1) to instruct it\nto set up alternates for the newly created repository. This allows it to\nreuse objects from the source repository so that in the best case we\ndon't have to clone all objects over.\n\nThose options are handled by the confusingly named `setup_reference()`\nfunction -- without the above context, one might rightfully believe that\nit was about refs, not about alternates. The function itself is rather\nsimple: we loop through all provided alternate paths and then, if such\nan alternate is valid, we write it to the object database.\n\nIn subsequent commits we're about to consolidate the complete setup of\nalternates into this function, and furthermore we'll refactor the setup\nof the object database to handle doing this for us instead of writing\nthe alterantes into it one by one.\n\nPrepare for this refactoring by collecting the alternates into a strvec.\nRename the function to `collect_alternates()` to clarify its scope.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------\n 1 file changed, 28 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 8c990ce0cc..8eae3ac7d9 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)\n \treturn canon;\n }\n \n-static int add_one_reference(struct string_list_item *item, void *cb_data)\n+struct add_one_alternate_data {\n+\tstruct strvec *alternates;\n+\tint required;\n+};\n+\n+static int add_one_alternate(struct string_list_item *item, void *cb_data)\n {\n+\tstruct add_one_alternate_data *data = cb_data;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint *required = cb_data;\n \tchar *ref_git = compute_alternate_path(item->string, &err);\n \n \tif (!ref_git) {\n-\t\tif (*required)\n+\t\tif (data->required)\n \t\t\tdie(\"%s\", err.buf);\n \t\telse\n \t\t\tfprintf(stderr,\n \t\t\t\t_(\"info: Could not add alternate for '%s': %s\\n\"),\n \t\t\t\titem->string, err.buf);\n \t} else {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\tstrbuf_addf(&sb, \"%s/objects\", ref_git);\n-\t\todb_add_to_alternates_file(the_repository->objects, sb.buf);\n-\t\tstrbuf_release(&sb);\n+\t\tstrvec_pushf(data->alternates, \"%s/objects\", ref_git);\n \t}\n \n \tstrbuf_release(&err);\n@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n-static void setup_reference(void)\n+static void collect_alternates(struct strvec *alternates)\n {\n-\tint required = 1;\n-\tfor_each_string_list(&option_required_reference,\n-\t\t\t     add_one_reference, &required);\n-\trequired = 0;\n-\tfor_each_string_list(&option_optional_reference,\n-\t\t\t     add_one_reference, &required);\n+\tif (option_required_reference.nr || option_optional_reference.nr) {\n+\t\tstruct add_one_alternate_data data = {\n+\t\t\t.alternates = alternates,\n+\t\t\t.required = 1,\n+\t\t};\n+\n+\t\tfor_each_string_list(&option_required_reference,\n+\t\t\t\t     add_one_alternate, &data);\n+\t\tdata.required = 0;\n+\t\tfor_each_string_list(&option_optional_reference,\n+\t\t\t\t     add_one_alternate, &data);\n+\t}\n }\n \n static void mkdir_if_missing(const char *pathname, mode_t mode)\n@@ -999,6 +1007,7 @@ int cmd_clone(int argc,\n \t\t\t   N_(\"uri\"), N_(\"a URI for downloading bundles before fetching from origin remote\")),\n \t\tOPT_END()\n \t};\n+\tstruct strvec alternates = STRVEC_INIT;\n \n \tconst char * const builtin_clone_usage[] = {\n \t\tN_(\"git clone [<options>] [--] <repo> [<dir>]\"),\n@@ -1343,8 +1352,10 @@ int cmd_clone(int argc,\n \t\twarning(_(\"--local is ignored\"));\n \n \tcreate_object_database(the_repository);\n-\tif (option_required_reference.nr || option_optional_reference.nr)\n-\t\tsetup_reference();\n+\tcollect_alternates(&alternates);\n+\n+\tfor (size_t i = 0; i < alternates.nr; i++)\n+\t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n \n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\n@@ -1641,6 +1652,7 @@ int cmd_clone(int argc,\n \tstring_list_clear(&option_not, 0);\n \tstring_list_clear(&option_config, 0);\n \tstring_list_clear(&server_options, 0);\n+\tstrvec_clear(&alternates);\n \n \tfree(remote_name);\n \tstrbuf_release(&reflog_msg);\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551177","messageId":"20260825-pks-odb-write-alternates-at-creation-time-v1-4-911513ba95c3@pks.im","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im","subject":"[PATCH 4/8] builtin/clone: move setup of alternates for shared local clones","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:11:53Z","receivedAt":"2026-08-25T14:12:45Z","isPatch":true,"body":"When cloning a local repository with \"--shared\" we add that repository\nto the new repository's alternates. This is done in `clone_local()`,\nwhich is responsible for performing local clones.\n\nMove the logic into `collect_alternates()` to unify our setup of\nalternates. Furthermore, this will allow us to set up alternates right\nat creation time of the object database.\n\nNote that the logic for cloning a local repository with \"--no-shared\" is\nnot yet part of `collect_alternates()`. This will be handled in the next\ncommit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 23 ++++++++++++++---------\n 1 file changed, 14 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 8eae3ac7d9..08c8f5a94f 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n-static void collect_alternates(struct strvec *alternates)\n+static void collect_alternates(struct strvec *alternates,\n+\t\t\t       const char *src_repo, bool is_local)\n {\n \tif (option_required_reference.nr || option_optional_reference.nr) {\n \t\tstruct add_one_alternate_data data = {\n@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)\n \t\tfor_each_string_list(&option_optional_reference,\n \t\t\t\t     add_one_alternate, &data);\n \t}\n+\n+\tif (is_local) {\n+\t\tstruct strbuf commondir = STRBUF_INIT;\n+\n+\t\tget_common_dir(&commondir, src_repo);\n+\t\tif (option_shared)\n+\t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n+\n+\t\tstrbuf_release(&commondir);\n+\t}\n }\n \n static void mkdir_if_missing(const char *pathname, mode_t mode)\n@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,\n \n static void clone_local(const char *src_repo, const char *dest_repo)\n {\n-\tif (option_shared) {\n-\t\tstruct strbuf alt = STRBUF_INIT;\n-\t\tget_common_dir(&alt, src_repo);\n-\t\tstrbuf_addstr(&alt, \"/objects\");\n-\t\todb_add_to_alternates_file(the_repository->objects, alt.buf);\n-\t\tstrbuf_release(&alt);\n-\t} else {\n+\tif (!option_shared) {\n \t\tstruct strbuf src = STRBUF_INIT;\n \t\tstruct strbuf dest = STRBUF_INIT;\n \t\tget_common_dir(&src, src_repo);\n@@ -1352,7 +1357,7 @@ int cmd_clone(int argc,\n \t\twarning(_(\"--local is ignored\"));\n \n \tcreate_object_database(the_repository);\n-\tcollect_alternates(&alternates);\n+\tcollect_alternates(&alternates, path, is_local);\n \n \tfor (size_t i = 0; i < alternates.nr; i++)\n \t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551178","messageId":"20260825-pks-odb-write-alternates-at-creation-time-v1-5-911513ba95c3@pks.im","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im","subject":"[PATCH 5/8] builtin/clone: move setup of alternates for non-shared local clones","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:11:54Z","receivedAt":"2026-08-25T14:12:47Z","isPatch":true,"body":"Similar as in the preceding commit, move the setup of alternates for\nlocal clones with \"--no-shared\" into `collect_alternates()`. With this\nstep, the complete setup of alternates is now handled by that function.\n\nNote that besides moving stuff around, it also fixes a bug: previously,\nwe did not know to resolve the referenced repository's common directory.\nConsequently, when referencing a worktree we failed to resolve\nalternates. But as `collect_alternates()` already knows to resolve the\ncommondir for \"--local\" we can simply reuse this resolved path for our\npurpose.\n\nAdd two tests, the first one of which exercises this bug to avoid future\nregressions. The second patch ensures that we properly handle relative\nalternates for a referenced worktree.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c            | 34 +++++++++++++++++++++++-----------\n t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 48 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 08c8f5a94f..2e3473fddf 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)\n \treturn 0;\n }\n \n-static void copy_alternates(struct strbuf *src, const char *src_repo)\n+static void read_alternates(struct strvec *alternates, const char *src_repo)\n {\n \t/*\n \t * Read from the source objects/info/alternates file\n@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \t * to turn entries with paths relative to the original\n \t * absolute, so that they can be used in the new repository.\n \t */\n-\tFILE *in = xfopen(src->buf, \"r\");\n+\tFILE *in;\n+\tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf line = STRBUF_INIT;\n \n+\tstrbuf_addf(&path, \"%s/objects/info/alternates\", src_repo);\n+\n+\tin = fopen(path.buf, \"r\");\n+\tif (!in) {\n+\t\tif (errno == ENOENT)\n+\t\t\tgoto out;\n+\t\tdie_errno(\"could not read alternates file '%s'\", path.buf);\n+\t}\n+\n \twhile (strbuf_getline(&line, in) != EOF) {\n \t\tchar *abs_path;\n \t\tif (!line.len || line.buf[0] == '#')\n \t\t\tcontinue;\n \t\tif (is_absolute_path(line.buf)) {\n-\t\t\todb_add_to_alternates_file(the_repository->objects,\n-\t\t\t\t\t\t   line.buf);\n+\t\t\tstrvec_push(alternates, line.buf);\n \t\t\tcontinue;\n \t\t}\n \t\tabs_path = mkpathdup(\"%s/objects/%s\", src_repo, line.buf);\n \t\tif (!normalize_path_copy(abs_path, abs_path))\n-\t\t\todb_add_to_alternates_file(the_repository->objects,\n-\t\t\t\t\t\t   abs_path);\n+\t\t\tstrvec_push(alternates, abs_path);\n \t\telse\n \t\t\twarning(\"skipping invalid relative alternate: %s/%s\",\n \t\t\t\tsrc_repo, line.buf);\n \t\tfree(abs_path);\n \t}\n+\n+out:\n+\tstrbuf_release(&path);\n \tstrbuf_release(&line);\n-\tfclose(in);\n+\tif (in)\n+\t\tfclose(in);\n }\n \n static void collect_alternates(struct strvec *alternates,\n@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,\n \t\tget_common_dir(&commondir, src_repo);\n \t\tif (option_shared)\n \t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n+\t\telse\n+\t\t\tread_alternates(alternates, commondir.buf);\n \n \t\tstrbuf_release(&commondir);\n \t}\n@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,\n \t\t\tcontinue;\n \t\t}\n \n-\t\t/* Files that cannot be copied bit-for-bit... */\n-\t\tif (!fspathcmp(iter->relative_path, \"info/alternates\")) {\n-\t\t\tcopy_alternates(src, src_repo);\n+\t\t/* Alternates were already handled earlier. */\n+\t\tif (!fspathcmp(iter->relative_path, \"info/alternates\"))\n \t\t\tcontinue;\n-\t\t}\n \n \t\tif (unlink(dest->buf) && errno != ENOENT)\n \t\t\tdie_errno(_(\"failed to unlink '%s'\"), dest->buf);\ndiff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh\nindex 39a0c318df..9e4b98fdb8 100755\n--- a/t/t5604-clone-reference.sh\n+++ b/t/t5604-clone-reference.sh\n@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '\n \tgit clone --no-local --reference graph.git --dissociate orig clone\n '\n \n+test_expect_success 'local clone from linked worktree carries over alternates' '\n+\trm -fr base derived derived-wt dst expect &&\n+\tgit init base &&\n+\ttest_commit -C base one &&\n+\tgit clone --shared base derived &&\n+\tgit -C derived worktree add ../derived-wt &&\n+\tgit clone derived-wt dst &&\n+\techo \"$(pwd)/base/.git/objects\" >expect &&\n+\ttest_cmp expect dst/.git/objects/info/alternates &&\n+\tgit -C dst fsck\n+'\n+\n+test_expect_success 'local clone from linked worktree resolves relative alternates' '\n+\trm -fr base derived derived-wt dst expect &&\n+\tgit init base &&\n+\ttest_commit -C base one &&\n+\tgit clone --shared base derived &&\n+\techo \"../../../base/.git/objects\" >derived/.git/objects/info/alternates &&\n+\tgit -C derived worktree add ../derived-wt &&\n+\tgit clone derived-wt dst &&\n+\techo \"$(pwd)/base/.git/objects\" >expect &&\n+\ttest_cmp expect dst/.git/objects/info/alternates &&\n+\tgit -C dst fsck\n+'\n+\n test_done\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551179","messageId":"20260825-pks-odb-write-alternates-at-creation-time-v1-6-911513ba95c3@pks.im","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im","subject":"[PATCH 6/8] odb/source: support writing alternates when creating the database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:11:55Z","receivedAt":"2026-08-25T14:12:49Z","isPatch":true,"body":"Add the ability to write alternates when creating the object database.\nThis change allows us to remove the `write_alternates()` callback in a\nsubsequent patch.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++--\n odb/source.h       | 18 +++++++++++---\n setup.c            |  4 ++-\n 3 files changed, 89 insertions(+), 6 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex b7b3a297bb..5e77b21d9f 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -18,6 +18,7 @@\n #include \"run-command.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n+#include \"strmap.h\"\n #include \"strvec.h\"\n #include \"tree.h\"\n #include \"write-or-die.h\"\n@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)\n \todb_source_close(&files->packed->base);\n }\n \n-static int odb_source_files_create_on_disk(struct odb_source *source)\n+static int odb_source_files_create_on_disk(struct odb_source *source,\n+\t\t\t\t\t   const struct odb_create_on_disk_options *opts)\n {\n \tstruct strbuf path = STRBUF_INIT;\n+\tstruct strset seen = STRSET_INIT;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tFILE *f = NULL;\n+\tint ret;\n \n \tsafe_create_dir(source->odb->repo, source->path, 1);\n \n@@ -64,8 +70,71 @@ static int odb_source_files_create_on_disk(struct odb_source *source)\n \tstrbuf_addf(&path, \"%s/info\", source->path);\n \tsafe_create_dir(source->odb->repo, path.buf, 1);\n \n+\tif (opts->alternates && opts->alternates->nr) {\n+\t\tstrbuf_reset(&path);\n+\t\tstrbuf_addf(&path, \"%s/info/alternates\", source->path);\n+\n+\t\t/*\n+\t\t * The alternates file may already exist, e.g. when it has been\n+\t\t * seeded from a template directory. Read any preexisting\n+\t\t * entries so that we don't end up writing duplicates.\n+\t\t */\n+\t\tf = fopen(path.buf, \"r\");\n+\t\tif (f) {\n+\t\t\twhile (strbuf_getline(&line, f) != EOF)\n+\t\t\t\tstrset_add(&seen, line.buf);\n+\n+\t\t\tif (ferror(f)) {\n+\t\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\tfclose(f);\n+\t\t} else if (errno != ENOENT) {\n+\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tf = fopen(path.buf, \"a\");\n+\t\tif (!f) {\n+\t\t\tret = error_errno(_(\"unable to open alternates file for writing\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (size_t i = 0; i < opts->alternates->nr; i++) {\n+\t\t\tconst char *alternate = opts->alternates->v[i];\n+\n+\t\t\tif (!strset_add(&seen, alternate))\n+\t\t\t\tcontinue;\n+\n+\t\t\tfprintf(f, \"%s\\n\", alternate);\n+\t\t}\n+\n+\t\tif (ferror(f)) {\n+\t\t\tret = error_errno(_(\"unable to write alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tret = fclose(f);\n+\t\tf = NULL;\n+\t\tif (ret) {\n+\t\t\tret = error_errno(_(\"unable to write alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+\t/* Reprepare the object database to activate alternates. */\n+\todb_reprepare(source->odb);\n+\n+\tret = 0;\n+\n+out:\n+\tif (f)\n+\t\tfclose(f);\n+\tstrbuf_release(&line);\n \tstrbuf_release(&path);\n-\treturn 0;\n+\tstrset_clear(&seen);\n+\treturn ret;\n }\n \n static void odb_source_files_prepare(struct odb_source *source,\ndiff --git a/odb/source.h b/odb/source.h\nindex ea8675247e..86b82fab3b 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -5,6 +5,7 @@\n #include \"object.h\"\n #include \"odb.h\"\n #include \"odb/transaction.h\"\n+#include \"strvec.h\"\n \n enum odb_source_type {\n \t/*\n@@ -26,6 +27,15 @@ enum odb_source_type {\n \tODB_SOURCE_INMEMORY,\n };\n \n+struct odb_create_on_disk_options {\n+\t/*\n+\t * Alternates that shall be written into the newly created object\n+\t * database. Whether or not this option can be handled is specific to\n+\t * the backend.\n+\t */\n+\tconst struct strvec *alternates;\n+};\n+\n /*\n  * Convert between the enum and its name. Returns the equivalent of \"unknown\"\n  * for unknown types.\n@@ -106,7 +116,8 @@ struct odb_source {\n \t * This callback may be NULL in case the source does not need any\n \t * on-disk setup.\n \t */\n-\tint (*create_on_disk)(struct odb_source *source);\n+\tint (*create_on_disk)(struct odb_source *source,\n+\t\t\t      const struct odb_create_on_disk_options *opts);\n \n \t/*\n \t * This callback is expected to prepare the source so that it becomes\n@@ -356,11 +367,12 @@ static inline void odb_source_close(struct odb_source *source)\n  * Create on-disk data structures that are required for this source to operate\n  * correctly. Returns 0 on success, a negative error code otherwise.\n  */\n-static inline int odb_source_create_on_disk(struct odb_source *source)\n+static inline int odb_source_create_on_disk(struct odb_source *source,\n+\t\t\t\t\t    const struct odb_create_on_disk_options *opts)\n {\n \tif (!source->create_on_disk)\n \t\treturn 0;\n-\treturn source->create_on_disk(source);\n+\treturn source->create_on_disk(source, opts);\n }\n \n /*\ndiff --git a/setup.c b/setup.c\nindex e654e27d05..426cc7dff8 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,\n \n void create_object_database(struct repository *repo)\n {\n+\tstruct odb_create_on_disk_options opts = { 0 };\n+\n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\n \t * so that the repository can be discovered regardless of the backend\n@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)\n \n \trepo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);\n \n-\tif (odb_source_create_on_disk(repo->objects->sources) < 0)\n+\tif (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)\n \t\tdie(_(\"failed creating object database\"));\n }\n \n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551180","messageId":"20260825-pks-odb-write-alternates-at-creation-time-v1-7-911513ba95c3@pks.im","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im","subject":"[PATCH 7/8] builtin/clone: write alternates via `odb_create_on_disk()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:11:56Z","receivedAt":"2026-08-25T14:12:52Z","isPatch":true,"body":"When creating a repository with alternates we first initialize the\nobject database and then write alternates to it in a separate step. This\nis unfortunate due to a couple of reasons:\n\n  - It requires us to have a `write_alternates()` callback, which is\n    unfortunate as we never even write alternates to an object database\n    after it has been created.\n\n  - We're about to make alternates an implementation detail of the\n    object database's backend in a future patch series, so having this\n    callback is suboptimal there.\n\n  - The backend has more flexibility with how exactly alternates are\n    configured when it itself is in full control over their setup at the\n    time where it creates the object database itself.\n\nWe have thus introduced the ability to write alternates right at\ncreation time in the preceding commits, and we have unified setup of\nalternates into a single location. All that's left to do for us now is\nto wire up alternates as an option for the database creation.\n\nDo so.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 5 +----\n setup.c         | 9 ++++++---\n setup.h         | 2 +-\n 3 files changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 2e3473fddf..48ac379b1d 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1368,11 +1368,8 @@ int cmd_clone(int argc,\n \tif (option_local > 0 && !is_local)\n \t\twarning(_(\"--local is ignored\"));\n \n-\tcreate_object_database(the_repository);\n \tcollect_alternates(&alternates, path, is_local);\n-\n-\tfor (size_t i = 0; i < alternates.nr; i++)\n-\t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n+\tcreate_object_database(the_repository, &alternates);\n \n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\ndiff --git a/setup.c b/setup.c\nindex 426cc7dff8..cfa286ff59 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,\n \treturn reinit;\n }\n \n-void create_object_database(struct repository *repo)\n+void create_object_database(struct repository *repo,\n+\t\t\t    const struct strvec *alternates)\n {\n-\tstruct odb_create_on_disk_options opts = { 0 };\n+\tstruct odb_create_on_disk_options opts = {\n+\t\t.alternates = alternates,\n+\t};\n \n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\n@@ -2907,7 +2910,7 @@ int init_db(struct repository *repo,\n \tif (!(flags & INIT_DB_SKIP_REFDB))\n \t\tcreate_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);\n \tif (!(flags & INIT_DB_SKIP_ODB))\n-\t\tcreate_object_database(repo);\n+\t\tcreate_object_database(repo, NULL);\n \n \tstartup_info->have_repository = 1;\n \ndiff --git a/setup.h b/setup.h\nindex 570ebcd150..34e86dad37 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -277,7 +277,7 @@ void initialize_repository_version(struct repository *repo,\n \t\t\t\t   enum ref_storage_format ref_storage_format,\n \t\t\t\t   int reinit);\n void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);\n-void create_object_database(struct repository *repo);\n+void create_object_database(struct repository *repo, const struct strvec *alternates);\n \n /*\n  * NOTE NOTE NOTE!!\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551181","messageId":"20260825-pks-odb-write-alternates-at-creation-time-v1-8-911513ba95c3@pks.im","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im","subject":"[PATCH 8/8] odb/source: remove the ability to write alternates","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-25T14:11:57Z","receivedAt":"2026-08-25T14:12:54Z","isPatch":true,"body":"There are no users of `odb_source_write_alternates()` in our tree\nanymore. Remove that function and its supporting infrastructure.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c                 |  9 ---------\n odb.h                 |  7 -------\n odb/source-files.c    | 55 ---------------------------------------------------\n odb/source-inmemory.c |  7 -------\n odb/source-loose.c    |  7 -------\n odb/source-packed.c   |  7 -------\n odb/source.h          | 26 ------------------------\n 7 files changed, 118 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 67d98d64fc..b531cf8fb3 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *\n \treturn alternate;\n }\n \n-void odb_add_to_alternates_file(struct object_database *odb,\n-\t\t\t\tconst char *dir)\n-{\n-\tint ret = odb_source_write_alternate(odb->sources, dir);\n-\tif (ret < 0)\n-\t\tdie(NULL);\n-\todb_add_alternate_recursively(odb, dir, 0);\n-}\n-\n struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,\n \t\t\t\t\t\tconst char *dir)\n {\ndiff --git a/odb.h b/odb.h\nindex b9e0db56ec..2d002461f8 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,\n  */\n int odb_has_alternates(struct object_database *odb);\n \n-/*\n- * Add the directory to the on-disk alternates file; the new entry will also\n- * take effect in the current process.\n- */\n-void odb_add_to_alternates_file(struct object_database *odb,\n-\t\t\t\tconst char *dir);\n-\n /*\n  * Add the directory to the in-memory list of alternate sources (along with any\n  * recursive alternates it points to), but do not modify the on-disk alternates\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 5e77b21d9f..feef9e169a 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -4,7 +4,6 @@\n #include \"chdir-notify.h\"\n #include \"config.h\"\n #include \"gettext.h\"\n-#include \"lockfile.h\"\n #include \"object-file.h\"\n #include \"odb.h\"\n #include \"odb/source.h\"\n@@ -303,59 +302,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,\n \treturn 0;\n }\n \n-static int odb_source_files_write_alternate(struct odb_source *source,\n-\t\t\t\t\t    const char *alternate)\n-{\n-\tstruct lock_file lock = LOCK_INIT;\n-\tchar *path = xstrfmt(\"%s/%s\", source->path, \"info/alternates\");\n-\tFILE *in, *out;\n-\tint found = 0;\n-\tint ret;\n-\n-\trepo_hold_lock_file_for_update(source->odb->repo, &lock, path,\n-\t\t\t\t       LOCK_DIE_ON_ERROR);\n-\tout = fdopen_lock_file(&lock, \"w\");\n-\tif (!out) {\n-\t\tret = error_errno(_(\"unable to fdopen alternates lockfile\"));\n-\t\tgoto out;\n-\t}\n-\n-\tin = fopen(path, \"r\");\n-\tif (in) {\n-\t\tstruct strbuf line = STRBUF_INIT;\n-\n-\t\twhile (strbuf_getline(&line, in) != EOF) {\n-\t\t\tif (!strcmp(alternate, line.buf)) {\n-\t\t\t\tfound = 1;\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t\tfprintf_or_die(out, \"%s\\n\", line.buf);\n-\t\t}\n-\n-\t\tstrbuf_release(&line);\n-\t\tfclose(in);\n-\t} else if (errno != ENOENT) {\n-\t\tret = error_errno(_(\"unable to read alternates file\"));\n-\t\tgoto out;\n-\t}\n-\n-\tif (found) {\n-\t\trollback_lock_file(&lock);\n-\t} else {\n-\t\tfprintf_or_die(out, \"%s\\n\", alternate);\n-\t\tif (commit_lock_file(&lock)) {\n-\t\t\tret = error_errno(_(\"unable to move new alternates file into place\"));\n-\t\t\tgoto out;\n-\t\t}\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\tfree(path);\n-\treturn ret;\n-}\n-\n static int too_many_loose_objects(struct odb_source_files *files, int limit)\n {\n \tunsigned long loose_count;\n@@ -839,7 +785,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tfiles->base.write_object_stream = odb_source_files_write_object_stream;\n \tfiles->base.begin_transaction = odb_source_files_begin_transaction;\n \tfiles->base.read_alternates = odb_source_files_read_alternates;\n-\tfiles->base.write_alternate = odb_source_files_write_alternate;\n \tfiles->base.optimize = odb_source_files_optimize;\n \tfiles->base.optimize_required = odb_source_files_optimize_required;\n \ndiff --git a/odb/source-inmemory.c b/odb/source-inmemory.c\nindex 795672adf2..b00248dfb2 100644\n--- a/odb/source-inmemory.c\n+++ b/odb/source-inmemory.c\n@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t       const char *alternate UNUSED)\n-{\n-\treturn error(\"in-memory source does not support alternates\");\n-}\n-\n static void odb_source_inmemory_close(struct odb_source *source UNUSED)\n {\n }\n@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)\n \tsource->base.freshen_object = odb_source_inmemory_freshen_object;\n \tsource->base.begin_transaction = odb_source_inmemory_begin_transaction;\n \tsource->base.read_alternates = odb_source_inmemory_read_alternates;\n-\tsource->base.write_alternate = odb_source_inmemory_write_alternate;\n \n \treturn source;\n }\ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex bb3455dfbd..0f9b30bac1 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t    const char *alternate UNUSED)\n-{\n-\treturn error(\"loose source does not support alternates\");\n-}\n-\n static void odb_source_loose_clear_cache(struct odb_source_loose *loose)\n {\n \toidtree_clear(loose->cache);\n@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n \tloose->base.write_object_stream = odb_source_loose_write_object_stream;\n \tloose->base.begin_transaction = odb_source_loose_begin_transaction;\n \tloose->base.read_alternates = odb_source_loose_read_alternates;\n-\tloose->base.write_alternate = odb_source_loose_write_alternate;\n \n \tif (!is_absolute_path(loose->base.path))\n \t\tchdir_notify_register(NULL, odb_source_loose_reparent, loose);\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 630d955585..c2d253759c 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t     const char *alternate UNUSED)\n-{\n-\treturn error(\"packed backend cannot write alternates\");\n-}\n-\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n \tpacked->base.write_object_stream = odb_source_packed_write_object_stream;\n \tpacked->base.begin_transaction = odb_source_packed_begin_transaction;\n \tpacked->base.read_alternates = odb_source_packed_read_alternates;\n-\tpacked->base.write_alternate = odb_source_packed_write_alternate;\n \n \tif (!is_absolute_path(path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/odb/source.h b/odb/source.h\nindex 86b82fab3b..5ee769e839 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -287,19 +287,6 @@ struct odb_source {\n \tint (*read_alternates)(struct odb_source *source,\n \t\t\t       struct strvec *out);\n \n-\t/*\n-\t * This callback is expected to persist the singular alternate passed\n-\t * to it into its list of alternates. Any pre-existing alternates are\n-\t * expected to remain active. Subsequent calls to `read_alternates` are\n-\t * thus expected to yield the pre-existing list of alternates plus the\n-\t * newly added alternate appended to its end.\n-\t *\n-\t * The callback is expected to return 0 on success, a negative error\n-\t * code otherwise.\n-\t */\n-\tint (*write_alternate)(struct odb_source *source,\n-\t\t\t       const char *alternate);\n-\n \t/*\n \t * This callback is expected to optimize the object database source.\n \t * Returns 0 on success, a negative error code otherwise.\n@@ -519,19 +506,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,\n \treturn source->read_alternates(source, out);\n }\n \n-/*\n- * Write and persist a new alternate object database source for the given\n- * source. Any preexisting alternates are expected to stay valid, and the new\n- * alternate shall be appended to the end of the list.\n- *\n- * Returns 0 on success, a negative error code otherwise.\n- */\n-static inline int odb_source_write_alternate(struct odb_source *source,\n-\t\t\t\t\t      const char *alternate)\n-{\n-\treturn source->write_alternate(source, alternate);\n-}\n-\n /*\n  * Create a new transaction that can be used to write objects into a temporary\n  * staging area. The objects will only be persisted when the transaction is\n\n-- \n2.55.0.822.g20453c30eb.dirty\n\n"},{"id":"551417","messageId":"877blauwkm.fsf@emacs.iotcl.com","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-3-911513ba95c3@pks.im","subject":"Re: [PATCH 3/8] builtin/clone: refactor handling of \"--reference{,-if-able}\"","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-08-28T14:52:09Z","receivedAt":"2026-08-28T14:52:20Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index 8c990ce0cc..8eae3ac7d9 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n>  \tfclose(in);\n>  }\n>  \n> -static void setup_reference(void)\n> +static void collect_alternates(struct strvec *alternates)\n>  {\n> -\tint required = 1;\n> -\tfor_each_string_list(&option_required_reference,\n> -\t\t\t     add_one_reference, &required);\n> -\trequired = 0;\n> -\tfor_each_string_list(&option_optional_reference,\n> -\t\t\t     add_one_reference, &required);\n> +\tif (option_required_reference.nr || option_optional_reference.nr) {\n\nI was about to ask why you're adding this guard, but it looks a lot more\nclean with the next commit on top.\n\n> +\t\tstruct add_one_alternate_data data = {\n> +\t\t\t.alternates = alternates,\n> +\t\t\t.required = 1,\n> +\t\t};\n> +\n> +\t\tfor_each_string_list(&option_required_reference,\n> +\t\t\t\t     add_one_alternate, &data);\n> +\t\tdata.required = 0;\n> +\t\tfor_each_string_list(&option_optional_reference,\n> +\t\t\t\t     add_one_alternate, &data);\n> +\t}\n>  }\n\n-- \nLaters,\nToon\n"},{"id":"551418","messageId":"875x0uuwk1.fsf@emacs.iotcl.com","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-4-911513ba95c3@pks.im","subject":"Re: [PATCH 4/8] builtin/clone: move setup of alternates for shared local clones","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-08-28T14:52:30Z","receivedAt":"2026-08-28T14:52:39Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index 8eae3ac7d9..08c8f5a94f 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n>  \tfclose(in);\n>  }\n>  \n> -static void collect_alternates(struct strvec *alternates)\n> +static void collect_alternates(struct strvec *alternates,\n> +\t\t\t       const char *src_repo, bool is_local)\n>  {\n>  \tif (option_required_reference.nr || option_optional_reference.nr) {\n>  \t\tstruct add_one_alternate_data data = {\n> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)\n>  \t\tfor_each_string_list(&option_optional_reference,\n>  \t\t\t\t     add_one_alternate, &data);\n>  \t}\n> +\n> +\tif (is_local) {\n> +\t\tstruct strbuf commondir = STRBUF_INIT;\n> +\n> +\t\tget_common_dir(&commondir, src_repo);\n> +\t\tif (option_shared)\n> +\t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n\nAlso the use of `commondir` seems to be only used if option_shared is\nset, but it will be used in an else in a subsequential commit.\n\n> +\n> +\t\tstrbuf_release(&commondir);\n> +\t}\n>  }\n\n-- \nLaters,\nToon\n"},{"id":"551419","messageId":"874igeuwja.fsf@emacs.iotcl.com","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-5-911513ba95c3@pks.im","subject":"Re: [PATCH 5/8] builtin/clone: move setup of alternates for non-shared local clones","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-08-28T14:52:57Z","receivedAt":"2026-08-28T14:53:06Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Similar as in the preceding commit, move the setup of alternates for\n> local clones with \"--no-shared\" into `collect_alternates()`. With this\n> step, the complete setup of alternates is now handled by that function.\n>\n> Note that besides moving stuff around, it also fixes a bug: previously,\n> we did not know to resolve the referenced repository's common directory.\n> Consequently, when referencing a worktree we failed to resolve\n> alternates. But as `collect_alternates()` already knows to resolve the\n> commondir for \"--local\" we can simply reuse this resolved path for our\n> purpose.\n>\n> Add two tests, the first one of which exercises this bug to avoid future\n> regressions. The second patch ensures that we properly handle relative\n> alternates for a referenced worktree.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/clone.c            | 34 +++++++++++++++++++++++-----------\n>  t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++\n>  2 files changed, 48 insertions(+), 11 deletions(-)\n>\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index 08c8f5a94f..2e3473fddf 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)\n>  \treturn 0;\n>  }\n>  \n> -static void copy_alternates(struct strbuf *src, const char *src_repo)\n> +static void read_alternates(struct strvec *alternates, const char *src_repo)\n>  {\n>  \t/*\n>  \t * Read from the source objects/info/alternates file\n> @@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n>  \t * to turn entries with paths relative to the original\n>  \t * absolute, so that they can be used in the new repository.\n>  \t */\n> -\tFILE *in = xfopen(src->buf, \"r\");\n> +\tFILE *in;\n> +\tstruct strbuf path = STRBUF_INIT;\n>  \tstruct strbuf line = STRBUF_INIT;\n>  \n> +\tstrbuf_addf(&path, \"%s/objects/info/alternates\", src_repo);\n> +\n> +\tin = fopen(path.buf, \"r\");\n> +\tif (!in) {\n> +\t\tif (errno == ENOENT)\n> +\t\t\tgoto out;\n> +\t\tdie_errno(\"could not read alternates file '%s'\", path.buf);\n> +\t}\n> +\n>  \twhile (strbuf_getline(&line, in) != EOF) {\n>  \t\tchar *abs_path;\n>  \t\tif (!line.len || line.buf[0] == '#')\n>  \t\t\tcontinue;\n>  \t\tif (is_absolute_path(line.buf)) {\n> -\t\t\todb_add_to_alternates_file(the_repository->objects,\n> -\t\t\t\t\t\t   line.buf);\n> +\t\t\tstrvec_push(alternates, line.buf);\n>  \t\t\tcontinue;\n>  \t\t}\n>  \t\tabs_path = mkpathdup(\"%s/objects/%s\", src_repo, line.buf);\n>  \t\tif (!normalize_path_copy(abs_path, abs_path))\n> -\t\t\todb_add_to_alternates_file(the_repository->objects,\n> -\t\t\t\t\t\t   abs_path);\n> +\t\t\tstrvec_push(alternates, abs_path);\n>  \t\telse\n>  \t\t\twarning(\"skipping invalid relative alternate: %s/%s\",\n>  \t\t\t\tsrc_repo, line.buf);\n>  \t\tfree(abs_path);\n>  \t}\n> +\n> +out:\n> +\tstrbuf_release(&path);\n>  \tstrbuf_release(&line);\n> -\tfclose(in);\n> +\tif (in)\n> +\t\tfclose(in);\n\nWhy not put this before the `out` label and remove the if?\n\n>  }\n\n-- \nLaters,\nToon\n"},{"id":"551420","messageId":"8733vyuwid.fsf@emacs.iotcl.com","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-6-911513ba95c3@pks.im","subject":"Re: [PATCH 6/8] odb/source: support writing alternates when creating the database","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-08-28T14:53:30Z","receivedAt":"2026-08-28T14:53:41Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Add the ability to write alternates when creating the object database.\n> This change allows us to remove the `write_alternates()` callback in a\n> subsequent patch.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  odb/source-files.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++--\n>  odb/source.h       | 18 +++++++++++---\n>  setup.c            |  4 ++-\n>  3 files changed, 89 insertions(+), 6 deletions(-)\n>\n> diff --git a/odb/source.h b/odb/source.h\n> index ea8675247e..86b82fab3b 100644\n> --- a/odb/source.h\n> +++ b/odb/source.h\n> @@ -5,6 +5,7 @@\n>  #include \"object.h\"\n>  #include \"odb.h\"\n>  #include \"odb/transaction.h\"\n> +#include \"strvec.h\"\n\nI see `struct strvec;` is already defined below\nodb_source_type_to_name(). Can we not include this and put the new\nstruct below the strvec definition?\n\n-- \nLaters,\nToon\n"},{"id":"551421","messageId":"871pbiuwhw.fsf@emacs.iotcl.com","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-8-911513ba95c3@pks.im","subject":"Re: [PATCH 8/8] odb/source: remove the ability to write alternates","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-08-28T14:53:47Z","receivedAt":"2026-08-28T14:53:57Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> There are no users of `odb_source_write_alternates()` in our tree\n> anymore. Remove that function and its supporting infrastructure.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  odb.c                 |  9 ---------\n>  odb.h                 |  7 -------\n>  odb/source-files.c    | 55 ---------------------------------------------------\n>  odb/source-inmemory.c |  7 -------\n>  odb/source-loose.c    |  7 -------\n>  odb/source-packed.c   |  7 -------\n>  odb/source.h          | 26 ------------------------\n>  7 files changed, 118 deletions(-)\n>\n> diff --git a/odb.c b/odb.c\n> index 67d98d64fc..b531cf8fb3 100644\n> --- a/odb.c\n> +++ b/odb.c\n> @@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *\n>  \treturn alternate;\n>  }\n>  \n> -void odb_add_to_alternates_file(struct object_database *odb,\n> -\t\t\t\tconst char *dir)\n> -{\n> -\tint ret = odb_source_write_alternate(odb->sources, dir);\n> -\tif (ret < 0)\n> -\t\tdie(NULL);\n> -\todb_add_alternate_recursively(odb, dir, 0);\n> -}\n> -\n>  struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,\n>  \t\t\t\t\t\tconst char *dir)\n>  {\n> diff --git a/odb.h b/odb.h\n> index b9e0db56ec..2d002461f8 100644\n> --- a/odb.h\n> +++ b/odb.h\n> @@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,\n>   */\n>  int odb_has_alternates(struct object_database *odb);\n>  \n> -/*\n> - * Add the directory to the on-disk alternates file; the new entry will also\n> - * take effect in the current process.\n> - */\n> -void odb_add_to_alternates_file(struct object_database *odb,\n> -\t\t\t\tconst char *dir);\n> -\n>  /*\n>   * Add the directory to the in-memory list of alternate sources (along with any\n>   * recursive alternates it points to), but do not modify the on-disk alternates\n> diff --git a/odb/source-files.c b/odb/source-files.c\n> index 5e77b21d9f..feef9e169a 100644\n> --- a/odb/source-files.c\n> +++ b/odb/source-files.c\n> @@ -4,7 +4,6 @@\n>  #include \"chdir-notify.h\"\n>  #include \"config.h\"\n>  #include \"gettext.h\"\n> -#include \"lockfile.h\"\n>  #include \"object-file.h\"\n>  #include \"odb.h\"\n>  #include \"odb/source.h\"\n> @@ -303,59 +302,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,\n>  \treturn 0;\n>  }\n>  \n> -static int odb_source_files_write_alternate(struct odb_source *source,\n> -\t\t\t\t\t    const char *alternate)\n> -{\n> -\tstruct lock_file lock = LOCK_INIT;\n> -\tchar *path = xstrfmt(\"%s/%s\", source->path, \"info/alternates\");\n> -\tFILE *in, *out;\n> -\tint found = 0;\n> -\tint ret;\n> -\n> -\trepo_hold_lock_file_for_update(source->odb->repo, &lock, path,\n> -\t\t\t\t       LOCK_DIE_ON_ERROR);\n\nWhy is the new implementation not using a lockfile?\n\n-- \nLaters,\nToon\n"},{"id":"551431","messageId":"xmqqbjam3vpb.fsf@gitster.g","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-6-911513ba95c3@pks.im","subject":"Re: [PATCH 6/8] odb/source: support writing alternates when creating the database","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-28T19:13:04Z","receivedAt":"2026-08-28T19:13:06Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Add the ability to write alternates when creating the object database.\n> This change allows us to remove the `write_alternates()` callback in a\n> subsequent patch.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n\n> diff --git a/odb/source-files.c b/odb/source-files.c\n> index b7b3a297bb..5e77b21d9f 100644\n> --- a/odb/source-files.c\n> +++ b/odb/source-files.c\n> ...\n> @@ -64,8 +70,71 @@ static int odb_source_files_create_on_disk(struct odb_source *source)\n> +\tif (opts->alternates && opts->alternates->nr) {\n> +\t\tstrbuf_reset(&path);\n> +\t\tstrbuf_addf(&path, \"%s/info/alternates\", source->path);\n> +\n> +\t\t/*\n> +\t\t * The alternates file may already exist, e.g. when it has been\n> +\t\t * seeded from a template directory. Read any preexisting\n> +\t\t * entries so that we don't end up writing duplicates.\n> +\t\t */\n> +\t\tf = fopen(path.buf, \"r\");\n> +\t\tif (f) {\n> +\t\t\twhile (strbuf_getline(&line, f) != EOF)\n> +\t\t\t\tstrset_add(&seen, line.buf);\n> +\n> +\t\t\tif (ferror(f)) {\n> +\t\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n> +\n> +\t\t\tfclose(f);\n> +\t\t} else if (errno != ENOENT) {\n> +\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n> +\t\tf = fopen(path.buf, \"a\");\n> +\t\tif (!f) {\n> +\t\t\tret = error_errno(_(\"unable to open alternates file for writing\"));\n> +\t\t\tgoto out;\n> +\t\t}\n\nI understand that using 'a' instead of 'w' is an attempt to deal\nwith the potential TOCTOU problem, but shouldn't we be using the\nstandard lockfile API, which atomically adds (or fails to add) to\navoid leaving a partially written file?  Or does it not matter,\nsince this is done only once upon repository creation when nobody\nshould be looking at the files on the filesystem?\n\nThanks.\n"},{"id":"551521","messageId":"apU3xpOZf0b9P6wT@pks.im","threadId":"66216","inReplyTo":"874igeuwja.fsf@emacs.iotcl.com","subject":"Re: [PATCH 5/8] builtin/clone: move setup of alternates for non-shared local clones","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T08:13:58Z","receivedAt":"2026-08-31T08:14:07Z","isPatch":true,"body":"On Fri, Aug 28, 2026 at 04:52:57PM +0200, Toon Claes wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/builtin/clone.c b/builtin/clone.c\n> > index 08c8f5a94f..2e3473fddf 100644\n> > --- a/builtin/clone.c\n> > +++ b/builtin/clone.c\n> > @@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n> >  \t * to turn entries with paths relative to the original\n> >  \t * absolute, so that they can be used in the new repository.\n> >  \t */\n> > -\tFILE *in = xfopen(src->buf, \"r\");\n> > +\tFILE *in;\n> > +\tstruct strbuf path = STRBUF_INIT;\n> >  \tstruct strbuf line = STRBUF_INIT;\n> >  \n> > +\tstrbuf_addf(&path, \"%s/objects/info/alternates\", src_repo);\n> > +\n> > +\tin = fopen(path.buf, \"r\");\n> > +\tif (!in) {\n> > +\t\tif (errno == ENOENT)\n> > +\t\t\tgoto out;\n> > +\t\tdie_errno(\"could not read alternates file '%s'\", path.buf);\n> > +\t}\n> > +\n> >  \twhile (strbuf_getline(&line, in) != EOF) {\n> >  \t\tchar *abs_path;\n> >  \t\tif (!line.len || line.buf[0] == '#')\n> >  \t\t\tcontinue;\n> >  \t\tif (is_absolute_path(line.buf)) {\n> > -\t\t\todb_add_to_alternates_file(the_repository->objects,\n> > -\t\t\t\t\t\t   line.buf);\n> > +\t\t\tstrvec_push(alternates, line.buf);\n> >  \t\t\tcontinue;\n> >  \t\t}\n> >  \t\tabs_path = mkpathdup(\"%s/objects/%s\", src_repo, line.buf);\n> >  \t\tif (!normalize_path_copy(abs_path, abs_path))\n> > -\t\t\todb_add_to_alternates_file(the_repository->objects,\n> > -\t\t\t\t\t\t   abs_path);\n> > +\t\t\tstrvec_push(alternates, abs_path);\n> >  \t\telse\n> >  \t\t\twarning(\"skipping invalid relative alternate: %s/%s\",\n> >  \t\t\t\tsrc_repo, line.buf);\n> >  \t\tfree(abs_path);\n> >  \t}\n> > +\n> > +out:\n> > +\tstrbuf_release(&path);\n> >  \tstrbuf_release(&line);\n> > -\tfclose(in);\n> > +\tif (in)\n> > +\t\tfclose(in);\n> \n> Why not put this before the `out` label and remove the if?\n\nMostly because it feels fragile to me. If we were to ever extend this\nfunction to have another `goto out` it's easy to miss that we don't\nclose `in` anymore.\n\nPatrick\n"},{"id":"551522","messageId":"apU3zShTb3W3rit9@pks.im","threadId":"66216","inReplyTo":"8733vyuwid.fsf@emacs.iotcl.com","subject":"Re: [PATCH 6/8] odb/source: support writing alternates when creating the database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T08:14:05Z","receivedAt":"2026-08-31T08:14:12Z","isPatch":true,"body":"On Fri, Aug 28, 2026 at 04:53:30PM +0200, Toon Claes wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/odb/source.h b/odb/source.h\n> > index ea8675247e..86b82fab3b 100644\n> > --- a/odb/source.h\n> > +++ b/odb/source.h\n> > @@ -5,6 +5,7 @@\n> >  #include \"object.h\"\n> >  #include \"odb.h\"\n> >  #include \"odb/transaction.h\"\n> > +#include \"strvec.h\"\n> \n> I see `struct strvec;` is already defined below\n> odb_source_type_to_name(). Can we not include this and put the new\n> struct below the strvec definition?\n\nGood point, will do.\n\nPatrick\n"},{"id":"551523","messageId":"apU33eMVrjlWlB7X@pks.im","threadId":"66216","inReplyTo":"xmqqbjam3vpb.fsf@gitster.g","subject":"Re: [PATCH 6/8] odb/source: support writing alternates when creating the database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T08:14:21Z","receivedAt":"2026-08-31T08:14:28Z","isPatch":true,"body":"On Fri, Aug 28, 2026 at 12:13:04PM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > Add the ability to write alternates when creating the object database.\n> > This change allows us to remove the `write_alternates()` callback in a\n> > subsequent patch.\n> >\n> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> > ---\n> \n> > diff --git a/odb/source-files.c b/odb/source-files.c\n> > index b7b3a297bb..5e77b21d9f 100644\n> > --- a/odb/source-files.c\n> > +++ b/odb/source-files.c\n> > ...\n> > @@ -64,8 +70,71 @@ static int odb_source_files_create_on_disk(struct odb_source *source)\n> > +\tif (opts->alternates && opts->alternates->nr) {\n> > +\t\tstrbuf_reset(&path);\n> > +\t\tstrbuf_addf(&path, \"%s/info/alternates\", source->path);\n> > +\n> > +\t\t/*\n> > +\t\t * The alternates file may already exist, e.g. when it has been\n> > +\t\t * seeded from a template directory. Read any preexisting\n> > +\t\t * entries so that we don't end up writing duplicates.\n> > +\t\t */\n> > +\t\tf = fopen(path.buf, \"r\");\n> > +\t\tif (f) {\n> > +\t\t\twhile (strbuf_getline(&line, f) != EOF)\n> > +\t\t\t\tstrset_add(&seen, line.buf);\n> > +\n> > +\t\t\tif (ferror(f)) {\n> > +\t\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n> > +\t\t\t\tgoto out;\n> > +\t\t\t}\n> > +\n> > +\t\t\tfclose(f);\n> > +\t\t} else if (errno != ENOENT) {\n> > +\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n> > +\t\t\tgoto out;\n> > +\t\t}\n> > +\n> > +\t\tf = fopen(path.buf, \"a\");\n> > +\t\tif (!f) {\n> > +\t\t\tret = error_errno(_(\"unable to open alternates file for writing\"));\n> > +\t\t\tgoto out;\n> > +\t\t}\n> \n> I understand that using 'a' instead of 'w' is an attempt to deal\n> with the potential TOCTOU problem, but shouldn't we be using the\n> standard lockfile API, which atomically adds (or fails to add) to\n> avoid leaving a partially written file?  Or does it not matter,\n> since this is done only once upon repository creation when nobody\n> should be looking at the files on the filesystem?\n\nToon also asked the same, and to be honest I never even considered doing\nthis. It's the repository creation anyway, so it just didn't come to my\nmind that it could be racing. In theory though it can, even though it\nfeels quite contrived.\n\nIn any case, I don't see a strong reason why we shouldn't use a lockfile\nhere.\n\nPatrick\n"},{"id":"551524","messageId":"apU35PsS6kzMbQmd@pks.im","threadId":"66216","inReplyTo":"871pbiuwhw.fsf@emacs.iotcl.com","subject":"Re: [PATCH 8/8] odb/source: remove the ability to write alternates","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T08:14:28Z","receivedAt":"2026-08-31T08:14:35Z","isPatch":true,"body":"On Fri, Aug 28, 2026 at 04:53:47PM +0200, Toon Claes wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/odb/source-files.c b/odb/source-files.c\n> > index 5e77b21d9f..feef9e169a 100644\n> > --- a/odb/source-files.c\n> > +++ b/odb/source-files.c\n> > @@ -303,59 +302,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,\n> >  \treturn 0;\n> >  }\n> >  \n> > -static int odb_source_files_write_alternate(struct odb_source *source,\n> > -\t\t\t\t\t    const char *alternate)\n> > -{\n> > -\tstruct lock_file lock = LOCK_INIT;\n> > -\tchar *path = xstrfmt(\"%s/%s\", source->path, \"info/alternates\");\n> > -\tFILE *in, *out;\n> > -\tint found = 0;\n> > -\tint ret;\n> > -\n> > -\trepo_hold_lock_file_for_update(source->odb->repo, &lock, path,\n> > -\t\t\t\t       LOCK_DIE_ON_ERROR);\n> \n> Why is the new implementation not using a lockfile?\n\nJunio asked the same, and the only reason is that I simply didn't\nthink about using a lockfile at all. Will fix, thanks.\n\nPatrick\n"},{"id":"551529","messageId":"20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im","subject":"[PATCH v2 0/8] odb: write alternates at creation time","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T10:02:04Z","receivedAt":"2026-08-31T10:02:18Z","isPatch":true,"body":"Hi,\n\nwriting alternates into the object database currently happens via\n`odb_source_write_alternate()`. But while that creates the ability to\ncreate alternates at arbitrary points of a source's lifetime, we don't\nuse that functionality in the first place. Instead, we only ever write\nalternates when creating a new repository.\n\nThis design is suboptimal due to a couple of reasons:\n\n  - It requires us to have a `write_alternates()` callback, which is\n    overblown as we never even write alternates to an object database\n    after it has been created.\n\n  - We're about to make alternates an implementation detail of the\n    object database's backend in a future patch series, so alternate\n    implementations may not even support them.\n\n  - The backend has more flexibility with how exactly alternates are\n    configured when it itself is in full control over their setup at the\n    time where it creates the object database itself.\n\nThis patch series thus refactors how we handle alternates so that we\ndon't write them ad-hoc anymore. Instead, the series introduces a new\noption for `odb_source_create_on_disk()` that makes it handle those\nalternates at creation time.\n\nThis is part of the bigger goal of moving handling of alternates into\nthe \"files\" backend.\n\nThis series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)\nwith ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop\n`alternates_db` field, 2026-08-17) merged into it.\n\nChanges in v2:\n  - Use a lockfile to write \"info/alternates\" during creation time.\n  - Remove useless \"strvec.h\" include by reordering declarations a bit.\n  - Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (8):\n      builtin/clone: defer setup of the object database\n      builtin/clone: move around `setup_reference()`\n      builtin/clone: refactor handling of \"--reference{,-if-able}\"\n      builtin/clone: move setup of alternates for shared local clones\n      builtin/clone: move setup of alternates for non-shared local clones\n      odb/source: support writing alternates when creating the database\n      builtin/clone: write alternates via `odb_create_on_disk()`\n      odb/source: remove the ability to write alternates\n\n builtin/clone.c            | 108 +++++++++++++++++++++++--------------\n odb.c                      |   9 ----\n odb.h                      |   7 ---\n odb/source-files.c         | 130 ++++++++++++++++++++++++++-------------------\n odb/source-inmemory.c      |   7 ---\n odb/source-loose.c         |   7 ---\n odb/source-packed.c        |   7 ---\n odb/source.h               |  43 +++++----------\n setup.c                    |  15 ++++--\n setup.h                    |  15 ++++--\n t/t5604-clone-reference.sh |  25 +++++++++\n 11 files changed, 203 insertions(+), 170 deletions(-)\n\nRange-diff versus v1:\n\n1:  85b01b6b1b = 1:  ca4f283e20 builtin/clone: defer setup of the object database\n2:  1e131d0a84 = 2:  4849b826ed builtin/clone: move around `setup_reference()`\n3:  a6d33bf077 = 3:  870b994a9d builtin/clone: refactor handling of \"--reference{,-if-able}\"\n4:  fd88a949f5 = 4:  6268e14ecb builtin/clone: move setup of alternates for shared local clones\n5:  e18f135daa = 5:  c148c65508 builtin/clone: move setup of alternates for non-shared local clones\n6:  e0733d5d63 ! 6:  360b8e83db odb/source: support writing alternates when creating the database\n    @@ odb/source-files.c: static void odb_source_files_close(struct odb_source *source\n     +static int odb_source_files_create_on_disk(struct odb_source *source,\n     +\t\t\t\t\t   const struct odb_create_on_disk_options *opts)\n      {\n    ++\tstruct lock_file alternates_lock = LOCK_INIT;\n      \tstruct strbuf path = STRBUF_INIT;\n     +\tstruct strset seen = STRSET_INIT;\n     +\tstruct strbuf line = STRBUF_INIT;\n    -+\tFILE *f = NULL;\n     +\tint ret;\n      \n      \tsafe_create_dir(source->odb->repo, source->path, 1);\n    @@ odb/source-files.c: static int odb_source_files_create_on_disk(struct odb_source\n      \tsafe_create_dir(source->odb->repo, path.buf, 1);\n      \n     +\tif (opts->alternates && opts->alternates->nr) {\n    ++\t\tFILE *alternates, *orig;\n    ++\n     +\t\tstrbuf_reset(&path);\n     +\t\tstrbuf_addf(&path, \"%s/info/alternates\", source->path);\n     +\n    ++\t\trepo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,\n    ++\t\t\t\t\t       path.buf, LOCK_DIE_ON_ERROR);\n    ++\n    ++\t\talternates = fdopen_lock_file(&alternates_lock, \"w\");\n    ++\t\tif (!alternates) {\n    ++\t\t\tret = error_errno(_(\"unable to fdopen alternates lockfile\"));\n    ++\t\t\tgoto out;\n    ++\t\t}\n    ++\n     +\t\t/*\n     +\t\t * The alternates file may already exist, e.g. when it has been\n     +\t\t * seeded from a template directory. Read any preexisting\n     +\t\t * entries so that we don't end up writing duplicates.\n     +\t\t */\n    -+\t\tf = fopen(path.buf, \"r\");\n    -+\t\tif (f) {\n    -+\t\t\twhile (strbuf_getline(&line, f) != EOF)\n    ++\t\torig = fopen(path.buf, \"r\");\n    ++\t\tif (orig) {\n    ++\t\t\twhile (strbuf_getline(&line, orig) != EOF) {\n     +\t\t\t\tstrset_add(&seen, line.buf);\n    ++\t\t\t\tfprintf(alternates, \"%s\\n\", line.buf);\n    ++\t\t\t}\n     +\n    -+\t\t\tif (ferror(f)) {\n    ++\t\t\tif (ferror(orig)) {\n     +\t\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n    ++\t\t\t\tfclose(orig);\n     +\t\t\t\tgoto out;\n     +\t\t\t}\n     +\n    -+\t\t\tfclose(f);\n    ++\t\t\tfclose(orig);\n     +\t\t} else if (errno != ENOENT) {\n     +\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n     +\t\t\tgoto out;\n     +\t\t}\n     +\n    -+\t\tf = fopen(path.buf, \"a\");\n    -+\t\tif (!f) {\n    -+\t\t\tret = error_errno(_(\"unable to open alternates file for writing\"));\n    -+\t\t\tgoto out;\n    -+\t\t}\n    -+\n     +\t\tfor (size_t i = 0; i < opts->alternates->nr; i++) {\n     +\t\t\tconst char *alternate = opts->alternates->v[i];\n    -+\n     +\t\t\tif (!strset_add(&seen, alternate))\n     +\t\t\t\tcontinue;\n    -+\n    -+\t\t\tfprintf(f, \"%s\\n\", alternate);\n    ++\t\t\tfprintf(alternates, \"%s\\n\", alternate);\n     +\t\t}\n     +\n    -+\t\tif (ferror(f)) {\n    ++\t\tif (ferror(alternates)) {\n     +\t\t\tret = error_errno(_(\"unable to write alternates file\"));\n     +\t\t\tgoto out;\n     +\t\t}\n     +\n    -+\t\tret = fclose(f);\n    -+\t\tf = NULL;\n    -+\t\tif (ret) {\n    -+\t\t\tret = error_errno(_(\"unable to write alternates file\"));\n    ++\t\tif (commit_lock_file(&alternates_lock)) {\n    ++\t\t\tret = error_errno(_(\"unable to commit alternates file\"));\n     +\t\t\tgoto out;\n     +\t\t}\n     +\t}\n    @@ odb/source-files.c: static int odb_source_files_create_on_disk(struct odb_source\n     +\tret = 0;\n     +\n     +out:\n    -+\tif (f)\n    -+\t\tfclose(f);\n    ++\trollback_lock_file(&alternates_lock);\n     +\tstrbuf_release(&line);\n      \tstrbuf_release(&path);\n     -\treturn 0;\n    @@ odb/source-files.c: static int odb_source_files_create_on_disk(struct odb_source\n      static void odb_source_files_prepare(struct odb_source *source,\n     \n      ## odb/source.h ##\n    -@@\n    - #include \"object.h\"\n    - #include \"odb.h\"\n    - #include \"odb/transaction.h\"\n    -+#include \"strvec.h\"\n    - \n    - enum odb_source_type {\n    - \t/*\n    -@@ odb/source.h: enum odb_source_type {\n    - \tODB_SOURCE_INMEMORY,\n    - };\n    +@@ odb/source.h: struct object_id;\n    + struct odb_stream;\n    + struct strvec;\n      \n     +struct odb_create_on_disk_options {\n     +\t/*\n    @@ odb/source.h: enum odb_source_type {\n     +};\n     +\n      /*\n    -  * Convert between the enum and its name. Returns the equivalent of \"unknown\"\n    -  * for unknown types.\n    +  * The source is the part of the object database that stores the actual\n    +  * objects. It thus encapsulates the logic to read and write the specific\n     @@ odb/source.h: struct odb_source {\n      \t * This callback may be NULL in case the source does not need any\n      \t * on-disk setup.\n7:  24ae5658ea = 7:  2809430e28 builtin/clone: write alternates via `odb_create_on_disk()`\n8:  3bda635fb8 ! 8:  2abcd3b53c odb/source: remove the ability to write alternates\n    @@ odb.h: int odb_mkstemp(struct object_database *odb,\n       * recursive alternates it points to), but do not modify the on-disk alternates\n     \n      ## odb/source-files.c ##\n    -@@\n    - #include \"chdir-notify.h\"\n    - #include \"config.h\"\n    - #include \"gettext.h\"\n    --#include \"lockfile.h\"\n    - #include \"object-file.h\"\n    - #include \"odb.h\"\n    - #include \"odb/source.h\"\n     @@ odb/source-files.c: static int odb_source_files_read_alternates(struct odb_source *source,\n      \treturn 0;\n      }\n\n---\nbase-commit: afa255aeb620346d56a2c01fb5ae9163513c56d7\nchange-id: 20260813-pks-odb-write-alternates-at-creation-time-64010deb94a0\n\n"},{"id":"551530","messageId":"20260831-pks-odb-write-alternates-at-creation-time-v2-1-aecd2382ba1c@pks.im","threadId":"66216","inReplyTo":"20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im","subject":"[PATCH v2 1/8] builtin/clone: defer setup of the object database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T10:02:05Z","receivedAt":"2026-08-31T10:02:19Z","isPatch":true,"body":"When cloning a repository we defer initialization of the reference\ndatabase. This is because we don't yet know all details required for us\nto initialize the refdb in the first place. Most importantly, what we\nare missing is information about the object hash.\n\nWe don't do the same thing for the object database yet, but here we\nessentially have the same problem. While the \"files\" database does not\nneed any information about the object format at creation time, alternate\nbackends are likely to require that information so that they can\nproperly set up their data structures.\n\nBesides this forward-looking future proofing though, we also have a\nsecond use case for deferring initialization of the object database,\nnamely alternates. When initializing the object database we do not yet\nknow whether we'll need alternates or not because this depends on the\nrepository we're about to clone from. If it is a local repository and\nthe user has passed \"--refernce{,-if-able}\", then we will end up writing\nalternates into the object database.\n\nThe ugly part though is that we cannot determine where the repository is\ngetting cloned from before it has been initialized. While we of course\nalready have access to the user-provided URI, that URI can be very well\nrewritten via \"url.<base>.insteadOf\". We can of course read the global-\nand system-level configuration to resolve it. But we explicitly resolve\nthe URI a second time after we have initialized the repository because\nit can happen that we copy a \".git/config\" over from our templates, and\nthat file may cause us to rewrite the path.\n\nIn a subsequent commit though we'll start to write alternates as part of\nthe repository initialization, so we'll need to have the URI properly\nresolved before we can initialize the object database. This is ugly, but\nas mentioned above it makes sense for us to defer its initialization\nanyway so that we also know about the object hash already.\n\nIntroduce a new flag that makes `init_db()` skip initializing the object\ndatabase. Expose `create_object_database()` and make use of it after we\nhave resolved the URI.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 12 ++++++++----\n setup.c         |  8 +++++---\n setup.h         | 15 ++++++++++-----\n 3 files changed, 23 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 5b25cca510..0a67492ebd 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1184,11 +1184,14 @@ int cmd_clone(int argc,\n \t * database. We do not yet know about the object format of the\n \t * repository, and reference backends may persist that information into\n \t * their on-disk data structures.\n+\t *\n+\t * Furthermore, we skip initializing the object database so that we can\n+\t * first resolve potential alternates before creating it.\n \t */\n \tinit_db(the_repository, git_dir, real_git_dir, work_tree, option_template,\n \t\tGIT_HASH_UNKNOWN, ref_storage_format, NULL,\n \t\tdo_not_override_repo_unix_permissions,\n-\t\tINIT_DB_QUIET | INIT_DB_SKIP_REFDB);\n+\t\tINIT_DB_QUIET | INIT_DB_SKIP_REFDB | INIT_DB_SKIP_ODB);\n \n \tif (real_git_dir) {\n \t\tfree((char *)git_dir);\n@@ -1311,9 +1314,6 @@ int cmd_clone(int argc,\n \t\tstrbuf_reset(&key);\n \t}\n \n-\tif (option_required_reference.nr || option_optional_reference.nr)\n-\t\tsetup_reference();\n-\n \tremote = remote_get_early(remote_name);\n \n \tif (!option_rev)\n@@ -1342,6 +1342,10 @@ int cmd_clone(int argc,\n \tif (option_local > 0 && !is_local)\n \t\twarning(_(\"--local is ignored\"));\n \n+\tcreate_object_database(the_repository);\n+\tif (option_required_reference.nr || option_optional_reference.nr)\n+\t\tsetup_reference();\n+\n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\n \ttransport->family = family;\ndiff --git a/setup.c b/setup.c\nindex d90654f584..e654e27d05 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,\n \treturn reinit;\n }\n \n-static void create_object_database(struct repository *repo)\n+void create_object_database(struct repository *repo)\n {\n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\n@@ -2829,7 +2829,8 @@ int init_db(struct repository *repo,\n \t    const char *template_dir, int hash,\n \t    enum ref_storage_format ref_storage_format,\n \t    const char *initial_branch,\n-\t    int init_shared_repository, unsigned int flags)\n+\t    int init_shared_repository,\n+\t    enum init_db_flags flags)\n {\n \tint reinit;\n \tint exist_ok = flags & INIT_DB_EXIST_OK;\n@@ -2903,7 +2904,8 @@ int init_db(struct repository *repo,\n \n \tif (!(flags & INIT_DB_SKIP_REFDB))\n \t\tcreate_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);\n-\tcreate_object_database(repo);\n+\tif (!(flags & INIT_DB_SKIP_ODB))\n+\t\tcreate_object_database(repo);\n \n \tstartup_info->have_repository = 1;\n \ndiff --git a/setup.h b/setup.h\nindex 763fd384e8..570ebcd150 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -256,9 +256,12 @@ int apply_repository_format(struct repository *repo,\n \n const char *get_template_dir(const char *option_template);\n \n-#define INIT_DB_QUIET      (1 << 0)\n-#define INIT_DB_EXIST_OK   (1 << 1)\n-#define INIT_DB_SKIP_REFDB (1 << 2)\n+enum init_db_flags {\n+\tINIT_DB_QUIET = (1 << 0),\n+\tINIT_DB_EXIST_OK = (1 << 1),\n+\tINIT_DB_SKIP_REFDB = (1 << 2),\n+\tINIT_DB_SKIP_ODB = (1 << 3),\n+};\n \n int init_db(struct repository *repo,\n \t    const char *git_dir,\n@@ -266,13 +269,15 @@ int init_db(struct repository *repo,\n \t    const char *worktree,\n \t    const char *template_dir, int hash_algo,\n \t    enum ref_storage_format ref_storage_format,\n-\t    const char *initial_branch, int init_shared_repository,\n-\t    unsigned int flags);\n+\t    const char *initial_branch,\n+\t    int init_shared_repository,\n+\t    enum init_db_flags flags);\n void initialize_repository_version(struct repository *repo,\n \t\t\t\t   int hash_algo,\n \t\t\t\t   enum ref_storage_format ref_storage_format,\n \t\t\t\t   int reinit);\n void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);\n+void create_object_database(struct repository *repo);\n \n /*\n  * NOTE NOTE NOTE!!\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551531","messageId":"20260831-pks-odb-write-alternates-at-creation-time-v2-2-aecd2382ba1c@pks.im","threadId":"66216","inReplyTo":"20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im","subject":"[PATCH v2 2/8] builtin/clone: move around `setup_reference()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T10:02:06Z","receivedAt":"2026-08-31T10:02:24Z","isPatch":true,"body":"In a subsequent commit, `setup_reference()` will start to call\n`copy_alternates()`. Prepare for this by moving the function further\ndown so that we can avoid adding a declaration.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 0a67492ebd..8c990ce0cc 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)\n \treturn 0;\n }\n \n-static void setup_reference(void)\n-{\n-\tint required = 1;\n-\tfor_each_string_list(&option_required_reference,\n-\t\t\t     add_one_reference, &required);\n-\trequired = 0;\n-\tfor_each_string_list(&option_optional_reference,\n-\t\t\t     add_one_reference, &required);\n-}\n-\n static void copy_alternates(struct strbuf *src, const char *src_repo)\n {\n \t/*\n@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n+static void setup_reference(void)\n+{\n+\tint required = 1;\n+\tfor_each_string_list(&option_required_reference,\n+\t\t\t     add_one_reference, &required);\n+\trequired = 0;\n+\tfor_each_string_list(&option_optional_reference,\n+\t\t\t     add_one_reference, &required);\n+}\n+\n static void mkdir_if_missing(const char *pathname, mode_t mode)\n {\n \tstruct stat st;\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551532","messageId":"20260831-pks-odb-write-alternates-at-creation-time-v2-3-aecd2382ba1c@pks.im","threadId":"66216","inReplyTo":"20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im","subject":"[PATCH v2 3/8] builtin/clone: refactor handling of \"--reference{,-if-able}\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T10:02:07Z","receivedAt":"2026-08-31T10:02:24Z","isPatch":true,"body":"Users can pass \"--reference{,-if-able}\" to git-clone(1) to instruct it\nto set up alternates for the newly created repository. This allows it to\nreuse objects from the source repository so that in the best case we\ndon't have to clone all objects over.\n\nThose options are handled by the confusingly named `setup_reference()`\nfunction -- without the above context, one might rightfully believe that\nit was about refs, not about alternates. The function itself is rather\nsimple: we loop through all provided alternate paths and then, if such\nan alternate is valid, we write it to the object database.\n\nIn subsequent commits we're about to consolidate the complete setup of\nalternates into this function, and furthermore we'll refactor the setup\nof the object database to handle doing this for us instead of writing\nthe alterantes into it one by one.\n\nPrepare for this refactoring by collecting the alternates into a strvec.\nRename the function to `collect_alternates()` to clarify its scope.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------\n 1 file changed, 28 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 8c990ce0cc..8eae3ac7d9 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)\n \treturn canon;\n }\n \n-static int add_one_reference(struct string_list_item *item, void *cb_data)\n+struct add_one_alternate_data {\n+\tstruct strvec *alternates;\n+\tint required;\n+};\n+\n+static int add_one_alternate(struct string_list_item *item, void *cb_data)\n {\n+\tstruct add_one_alternate_data *data = cb_data;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint *required = cb_data;\n \tchar *ref_git = compute_alternate_path(item->string, &err);\n \n \tif (!ref_git) {\n-\t\tif (*required)\n+\t\tif (data->required)\n \t\t\tdie(\"%s\", err.buf);\n \t\telse\n \t\t\tfprintf(stderr,\n \t\t\t\t_(\"info: Could not add alternate for '%s': %s\\n\"),\n \t\t\t\titem->string, err.buf);\n \t} else {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\tstrbuf_addf(&sb, \"%s/objects\", ref_git);\n-\t\todb_add_to_alternates_file(the_repository->objects, sb.buf);\n-\t\tstrbuf_release(&sb);\n+\t\tstrvec_pushf(data->alternates, \"%s/objects\", ref_git);\n \t}\n \n \tstrbuf_release(&err);\n@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n-static void setup_reference(void)\n+static void collect_alternates(struct strvec *alternates)\n {\n-\tint required = 1;\n-\tfor_each_string_list(&option_required_reference,\n-\t\t\t     add_one_reference, &required);\n-\trequired = 0;\n-\tfor_each_string_list(&option_optional_reference,\n-\t\t\t     add_one_reference, &required);\n+\tif (option_required_reference.nr || option_optional_reference.nr) {\n+\t\tstruct add_one_alternate_data data = {\n+\t\t\t.alternates = alternates,\n+\t\t\t.required = 1,\n+\t\t};\n+\n+\t\tfor_each_string_list(&option_required_reference,\n+\t\t\t\t     add_one_alternate, &data);\n+\t\tdata.required = 0;\n+\t\tfor_each_string_list(&option_optional_reference,\n+\t\t\t\t     add_one_alternate, &data);\n+\t}\n }\n \n static void mkdir_if_missing(const char *pathname, mode_t mode)\n@@ -999,6 +1007,7 @@ int cmd_clone(int argc,\n \t\t\t   N_(\"uri\"), N_(\"a URI for downloading bundles before fetching from origin remote\")),\n \t\tOPT_END()\n \t};\n+\tstruct strvec alternates = STRVEC_INIT;\n \n \tconst char * const builtin_clone_usage[] = {\n \t\tN_(\"git clone [<options>] [--] <repo> [<dir>]\"),\n@@ -1343,8 +1352,10 @@ int cmd_clone(int argc,\n \t\twarning(_(\"--local is ignored\"));\n \n \tcreate_object_database(the_repository);\n-\tif (option_required_reference.nr || option_optional_reference.nr)\n-\t\tsetup_reference();\n+\tcollect_alternates(&alternates);\n+\n+\tfor (size_t i = 0; i < alternates.nr; i++)\n+\t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n \n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\n@@ -1641,6 +1652,7 @@ int cmd_clone(int argc,\n \tstring_list_clear(&option_not, 0);\n \tstring_list_clear(&option_config, 0);\n \tstring_list_clear(&server_options, 0);\n+\tstrvec_clear(&alternates);\n \n \tfree(remote_name);\n \tstrbuf_release(&reflog_msg);\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551533","messageId":"20260831-pks-odb-write-alternates-at-creation-time-v2-4-aecd2382ba1c@pks.im","threadId":"66216","inReplyTo":"20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im","subject":"[PATCH v2 4/8] builtin/clone: move setup of alternates for shared local clones","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T10:02:08Z","receivedAt":"2026-08-31T10:02:28Z","isPatch":true,"body":"When cloning a local repository with \"--shared\" we add that repository\nto the new repository's alternates. This is done in `clone_local()`,\nwhich is responsible for performing local clones.\n\nMove the logic into `collect_alternates()` to unify our setup of\nalternates. Furthermore, this will allow us to set up alternates right\nat creation time of the object database.\n\nNote that the logic for cloning a local repository with \"--no-shared\" is\nnot yet part of `collect_alternates()`. This will be handled in the next\ncommit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 23 ++++++++++++++---------\n 1 file changed, 14 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 8eae3ac7d9..08c8f5a94f 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n-static void collect_alternates(struct strvec *alternates)\n+static void collect_alternates(struct strvec *alternates,\n+\t\t\t       const char *src_repo, bool is_local)\n {\n \tif (option_required_reference.nr || option_optional_reference.nr) {\n \t\tstruct add_one_alternate_data data = {\n@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)\n \t\tfor_each_string_list(&option_optional_reference,\n \t\t\t\t     add_one_alternate, &data);\n \t}\n+\n+\tif (is_local) {\n+\t\tstruct strbuf commondir = STRBUF_INIT;\n+\n+\t\tget_common_dir(&commondir, src_repo);\n+\t\tif (option_shared)\n+\t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n+\n+\t\tstrbuf_release(&commondir);\n+\t}\n }\n \n static void mkdir_if_missing(const char *pathname, mode_t mode)\n@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,\n \n static void clone_local(const char *src_repo, const char *dest_repo)\n {\n-\tif (option_shared) {\n-\t\tstruct strbuf alt = STRBUF_INIT;\n-\t\tget_common_dir(&alt, src_repo);\n-\t\tstrbuf_addstr(&alt, \"/objects\");\n-\t\todb_add_to_alternates_file(the_repository->objects, alt.buf);\n-\t\tstrbuf_release(&alt);\n-\t} else {\n+\tif (!option_shared) {\n \t\tstruct strbuf src = STRBUF_INIT;\n \t\tstruct strbuf dest = STRBUF_INIT;\n \t\tget_common_dir(&src, src_repo);\n@@ -1352,7 +1357,7 @@ int cmd_clone(int argc,\n \t\twarning(_(\"--local is ignored\"));\n \n \tcreate_object_database(the_repository);\n-\tcollect_alternates(&alternates);\n+\tcollect_alternates(&alternates, path, is_local);\n \n \tfor (size_t i = 0; i < alternates.nr; i++)\n \t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551534","messageId":"20260831-pks-odb-write-alternates-at-creation-time-v2-5-aecd2382ba1c@pks.im","threadId":"66216","inReplyTo":"20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im","subject":"[PATCH v2 5/8] builtin/clone: move setup of alternates for non-shared local clones","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T10:02:09Z","receivedAt":"2026-08-31T10:02:30Z","isPatch":true,"body":"Similar as in the preceding commit, move the setup of alternates for\nlocal clones with \"--no-shared\" into `collect_alternates()`. With this\nstep, the complete setup of alternates is now handled by that function.\n\nNote that besides moving stuff around, it also fixes a bug: previously,\nwe did not know to resolve the referenced repository's common directory.\nConsequently, when referencing a worktree we failed to resolve\nalternates. But as `collect_alternates()` already knows to resolve the\ncommondir for \"--local\" we can simply reuse this resolved path for our\npurpose.\n\nAdd two tests, the first one of which exercises this bug to avoid future\nregressions. The second patch ensures that we properly handle relative\nalternates for a referenced worktree.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c            | 34 +++++++++++++++++++++++-----------\n t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 48 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 08c8f5a94f..2e3473fddf 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)\n \treturn 0;\n }\n \n-static void copy_alternates(struct strbuf *src, const char *src_repo)\n+static void read_alternates(struct strvec *alternates, const char *src_repo)\n {\n \t/*\n \t * Read from the source objects/info/alternates file\n@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \t * to turn entries with paths relative to the original\n \t * absolute, so that they can be used in the new repository.\n \t */\n-\tFILE *in = xfopen(src->buf, \"r\");\n+\tFILE *in;\n+\tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf line = STRBUF_INIT;\n \n+\tstrbuf_addf(&path, \"%s/objects/info/alternates\", src_repo);\n+\n+\tin = fopen(path.buf, \"r\");\n+\tif (!in) {\n+\t\tif (errno == ENOENT)\n+\t\t\tgoto out;\n+\t\tdie_errno(\"could not read alternates file '%s'\", path.buf);\n+\t}\n+\n \twhile (strbuf_getline(&line, in) != EOF) {\n \t\tchar *abs_path;\n \t\tif (!line.len || line.buf[0] == '#')\n \t\t\tcontinue;\n \t\tif (is_absolute_path(line.buf)) {\n-\t\t\todb_add_to_alternates_file(the_repository->objects,\n-\t\t\t\t\t\t   line.buf);\n+\t\t\tstrvec_push(alternates, line.buf);\n \t\t\tcontinue;\n \t\t}\n \t\tabs_path = mkpathdup(\"%s/objects/%s\", src_repo, line.buf);\n \t\tif (!normalize_path_copy(abs_path, abs_path))\n-\t\t\todb_add_to_alternates_file(the_repository->objects,\n-\t\t\t\t\t\t   abs_path);\n+\t\t\tstrvec_push(alternates, abs_path);\n \t\telse\n \t\t\twarning(\"skipping invalid relative alternate: %s/%s\",\n \t\t\t\tsrc_repo, line.buf);\n \t\tfree(abs_path);\n \t}\n+\n+out:\n+\tstrbuf_release(&path);\n \tstrbuf_release(&line);\n-\tfclose(in);\n+\tif (in)\n+\t\tfclose(in);\n }\n \n static void collect_alternates(struct strvec *alternates,\n@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,\n \t\tget_common_dir(&commondir, src_repo);\n \t\tif (option_shared)\n \t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n+\t\telse\n+\t\t\tread_alternates(alternates, commondir.buf);\n \n \t\tstrbuf_release(&commondir);\n \t}\n@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,\n \t\t\tcontinue;\n \t\t}\n \n-\t\t/* Files that cannot be copied bit-for-bit... */\n-\t\tif (!fspathcmp(iter->relative_path, \"info/alternates\")) {\n-\t\t\tcopy_alternates(src, src_repo);\n+\t\t/* Alternates were already handled earlier. */\n+\t\tif (!fspathcmp(iter->relative_path, \"info/alternates\"))\n \t\t\tcontinue;\n-\t\t}\n \n \t\tif (unlink(dest->buf) && errno != ENOENT)\n \t\t\tdie_errno(_(\"failed to unlink '%s'\"), dest->buf);\ndiff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh\nindex 39a0c318df..9e4b98fdb8 100755\n--- a/t/t5604-clone-reference.sh\n+++ b/t/t5604-clone-reference.sh\n@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '\n \tgit clone --no-local --reference graph.git --dissociate orig clone\n '\n \n+test_expect_success 'local clone from linked worktree carries over alternates' '\n+\trm -fr base derived derived-wt dst expect &&\n+\tgit init base &&\n+\ttest_commit -C base one &&\n+\tgit clone --shared base derived &&\n+\tgit -C derived worktree add ../derived-wt &&\n+\tgit clone derived-wt dst &&\n+\techo \"$(pwd)/base/.git/objects\" >expect &&\n+\ttest_cmp expect dst/.git/objects/info/alternates &&\n+\tgit -C dst fsck\n+'\n+\n+test_expect_success 'local clone from linked worktree resolves relative alternates' '\n+\trm -fr base derived derived-wt dst expect &&\n+\tgit init base &&\n+\ttest_commit -C base one &&\n+\tgit clone --shared base derived &&\n+\techo \"../../../base/.git/objects\" >derived/.git/objects/info/alternates &&\n+\tgit -C derived worktree add ../derived-wt &&\n+\tgit clone derived-wt dst &&\n+\techo \"$(pwd)/base/.git/objects\" >expect &&\n+\ttest_cmp expect dst/.git/objects/info/alternates &&\n+\tgit -C dst fsck\n+'\n+\n test_done\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551535","messageId":"20260831-pks-odb-write-alternates-at-creation-time-v2-6-aecd2382ba1c@pks.im","threadId":"66216","inReplyTo":"20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im","subject":"[PATCH v2 6/8] odb/source: support writing alternates when creating the database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T10:02:10Z","receivedAt":"2026-08-31T10:02:32Z","isPatch":true,"body":"Add the ability to write alternates when creating the object database.\nThis change allows us to remove the `write_alternates()` callback in a\nsubsequent patch.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--\n odb/source.h       | 17 +++++++++---\n setup.c            |  4 ++-\n 3 files changed, 91 insertions(+), 6 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex b7b3a297bb..8fe65d91f8 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -18,6 +18,7 @@\n #include \"run-command.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n+#include \"strmap.h\"\n #include \"strvec.h\"\n #include \"tree.h\"\n #include \"write-or-die.h\"\n@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)\n \todb_source_close(&files->packed->base);\n }\n \n-static int odb_source_files_create_on_disk(struct odb_source *source)\n+static int odb_source_files_create_on_disk(struct odb_source *source,\n+\t\t\t\t\t   const struct odb_create_on_disk_options *opts)\n {\n+\tstruct lock_file alternates_lock = LOCK_INIT;\n \tstruct strbuf path = STRBUF_INIT;\n+\tstruct strset seen = STRSET_INIT;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tint ret;\n \n \tsafe_create_dir(source->odb->repo, source->path, 1);\n \n@@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)\n \tstrbuf_addf(&path, \"%s/info\", source->path);\n \tsafe_create_dir(source->odb->repo, path.buf, 1);\n \n+\tif (opts->alternates && opts->alternates->nr) {\n+\t\tFILE *alternates, *orig;\n+\n+\t\tstrbuf_reset(&path);\n+\t\tstrbuf_addf(&path, \"%s/info/alternates\", source->path);\n+\n+\t\trepo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,\n+\t\t\t\t\t       path.buf, LOCK_DIE_ON_ERROR);\n+\n+\t\talternates = fdopen_lock_file(&alternates_lock, \"w\");\n+\t\tif (!alternates) {\n+\t\t\tret = error_errno(_(\"unable to fdopen alternates lockfile\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/*\n+\t\t * The alternates file may already exist, e.g. when it has been\n+\t\t * seeded from a template directory. Read any preexisting\n+\t\t * entries so that we don't end up writing duplicates.\n+\t\t */\n+\t\torig = fopen(path.buf, \"r\");\n+\t\tif (orig) {\n+\t\t\twhile (strbuf_getline(&line, orig) != EOF) {\n+\t\t\t\tstrset_add(&seen, line.buf);\n+\t\t\t\tfprintf(alternates, \"%s\\n\", line.buf);\n+\t\t\t}\n+\n+\t\t\tif (ferror(orig)) {\n+\t\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n+\t\t\t\tfclose(orig);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\tfclose(orig);\n+\t\t} else if (errno != ENOENT) {\n+\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (size_t i = 0; i < opts->alternates->nr; i++) {\n+\t\t\tconst char *alternate = opts->alternates->v[i];\n+\t\t\tif (!strset_add(&seen, alternate))\n+\t\t\t\tcontinue;\n+\t\t\tfprintf(alternates, \"%s\\n\", alternate);\n+\t\t}\n+\n+\t\tif (ferror(alternates)) {\n+\t\t\tret = error_errno(_(\"unable to write alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (commit_lock_file(&alternates_lock)) {\n+\t\t\tret = error_errno(_(\"unable to commit alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+\t/* Reprepare the object database to activate alternates. */\n+\todb_reprepare(source->odb);\n+\n+\tret = 0;\n+\n+out:\n+\trollback_lock_file(&alternates_lock);\n+\tstrbuf_release(&line);\n \tstrbuf_release(&path);\n-\treturn 0;\n+\tstrset_clear(&seen);\n+\treturn ret;\n }\n \n static void odb_source_files_prepare(struct odb_source *source,\ndiff --git a/odb/source.h b/odb/source.h\nindex ea8675247e..63f1c0c531 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -36,6 +36,15 @@ struct object_id;\n struct odb_stream;\n struct strvec;\n \n+struct odb_create_on_disk_options {\n+\t/*\n+\t * Alternates that shall be written into the newly created object\n+\t * database. Whether or not this option can be handled is specific to\n+\t * the backend.\n+\t */\n+\tconst struct strvec *alternates;\n+};\n+\n /*\n  * The source is the part of the object database that stores the actual\n  * objects. It thus encapsulates the logic to read and write the specific\n@@ -106,7 +115,8 @@ struct odb_source {\n \t * This callback may be NULL in case the source does not need any\n \t * on-disk setup.\n \t */\n-\tint (*create_on_disk)(struct odb_source *source);\n+\tint (*create_on_disk)(struct odb_source *source,\n+\t\t\t      const struct odb_create_on_disk_options *opts);\n \n \t/*\n \t * This callback is expected to prepare the source so that it becomes\n@@ -356,11 +366,12 @@ static inline void odb_source_close(struct odb_source *source)\n  * Create on-disk data structures that are required for this source to operate\n  * correctly. Returns 0 on success, a negative error code otherwise.\n  */\n-static inline int odb_source_create_on_disk(struct odb_source *source)\n+static inline int odb_source_create_on_disk(struct odb_source *source,\n+\t\t\t\t\t    const struct odb_create_on_disk_options *opts)\n {\n \tif (!source->create_on_disk)\n \t\treturn 0;\n-\treturn source->create_on_disk(source);\n+\treturn source->create_on_disk(source, opts);\n }\n \n /*\ndiff --git a/setup.c b/setup.c\nindex e654e27d05..426cc7dff8 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,\n \n void create_object_database(struct repository *repo)\n {\n+\tstruct odb_create_on_disk_options opts = { 0 };\n+\n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\n \t * so that the repository can be discovered regardless of the backend\n@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)\n \n \trepo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);\n \n-\tif (odb_source_create_on_disk(repo->objects->sources) < 0)\n+\tif (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)\n \t\tdie(_(\"failed creating object database\"));\n }\n \n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551536","messageId":"20260831-pks-odb-write-alternates-at-creation-time-v2-7-aecd2382ba1c@pks.im","threadId":"66216","inReplyTo":"20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im","subject":"[PATCH v2 7/8] builtin/clone: write alternates via `odb_create_on_disk()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T10:02:11Z","receivedAt":"2026-08-31T10:02:35Z","isPatch":true,"body":"When creating a repository with alternates we first initialize the\nobject database and then write alternates to it in a separate step. This\nis unfortunate due to a couple of reasons:\n\n  - It requires us to have a `write_alternates()` callback, which is\n    unfortunate as we never even write alternates to an object database\n    after it has been created.\n\n  - We're about to make alternates an implementation detail of the\n    object database's backend in a future patch series, so having this\n    callback is suboptimal there.\n\n  - The backend has more flexibility with how exactly alternates are\n    configured when it itself is in full control over their setup at the\n    time where it creates the object database itself.\n\nWe have thus introduced the ability to write alternates right at\ncreation time in the preceding commits, and we have unified setup of\nalternates into a single location. All that's left to do for us now is\nto wire up alternates as an option for the database creation.\n\nDo so.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 5 +----\n setup.c         | 9 ++++++---\n setup.h         | 2 +-\n 3 files changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 2e3473fddf..48ac379b1d 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1368,11 +1368,8 @@ int cmd_clone(int argc,\n \tif (option_local > 0 && !is_local)\n \t\twarning(_(\"--local is ignored\"));\n \n-\tcreate_object_database(the_repository);\n \tcollect_alternates(&alternates, path, is_local);\n-\n-\tfor (size_t i = 0; i < alternates.nr; i++)\n-\t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n+\tcreate_object_database(the_repository, &alternates);\n \n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\ndiff --git a/setup.c b/setup.c\nindex 426cc7dff8..cfa286ff59 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,\n \treturn reinit;\n }\n \n-void create_object_database(struct repository *repo)\n+void create_object_database(struct repository *repo,\n+\t\t\t    const struct strvec *alternates)\n {\n-\tstruct odb_create_on_disk_options opts = { 0 };\n+\tstruct odb_create_on_disk_options opts = {\n+\t\t.alternates = alternates,\n+\t};\n \n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\n@@ -2907,7 +2910,7 @@ int init_db(struct repository *repo,\n \tif (!(flags & INIT_DB_SKIP_REFDB))\n \t\tcreate_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);\n \tif (!(flags & INIT_DB_SKIP_ODB))\n-\t\tcreate_object_database(repo);\n+\t\tcreate_object_database(repo, NULL);\n \n \tstartup_info->have_repository = 1;\n \ndiff --git a/setup.h b/setup.h\nindex 570ebcd150..34e86dad37 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -277,7 +277,7 @@ void initialize_repository_version(struct repository *repo,\n \t\t\t\t   enum ref_storage_format ref_storage_format,\n \t\t\t\t   int reinit);\n void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);\n-void create_object_database(struct repository *repo);\n+void create_object_database(struct repository *repo, const struct strvec *alternates);\n \n /*\n  * NOTE NOTE NOTE!!\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551537","messageId":"20260831-pks-odb-write-alternates-at-creation-time-v2-8-aecd2382ba1c@pks.im","threadId":"66216","inReplyTo":"20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im","subject":"[PATCH v2 8/8] odb/source: remove the ability to write alternates","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-31T10:02:12Z","receivedAt":"2026-08-31T10:02:37Z","isPatch":true,"body":"There are no users of `odb_source_write_alternates()` in our tree\nanymore. Remove that function and its supporting infrastructure.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c                 |  9 ---------\n odb.h                 |  7 -------\n odb/source-files.c    | 54 ---------------------------------------------------\n odb/source-inmemory.c |  7 -------\n odb/source-loose.c    |  7 -------\n odb/source-packed.c   |  7 -------\n odb/source.h          | 26 -------------------------\n 7 files changed, 117 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 67d98d64fc..b531cf8fb3 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *\n \treturn alternate;\n }\n \n-void odb_add_to_alternates_file(struct object_database *odb,\n-\t\t\t\tconst char *dir)\n-{\n-\tint ret = odb_source_write_alternate(odb->sources, dir);\n-\tif (ret < 0)\n-\t\tdie(NULL);\n-\todb_add_alternate_recursively(odb, dir, 0);\n-}\n-\n struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,\n \t\t\t\t\t\tconst char *dir)\n {\ndiff --git a/odb.h b/odb.h\nindex b9e0db56ec..2d002461f8 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,\n  */\n int odb_has_alternates(struct object_database *odb);\n \n-/*\n- * Add the directory to the on-disk alternates file; the new entry will also\n- * take effect in the current process.\n- */\n-void odb_add_to_alternates_file(struct object_database *odb,\n-\t\t\t\tconst char *dir);\n-\n /*\n  * Add the directory to the in-memory list of alternate sources (along with any\n  * recursive alternates it points to), but do not modify the on-disk alternates\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 8fe65d91f8..b3f340dff8 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -306,59 +306,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,\n \treturn 0;\n }\n \n-static int odb_source_files_write_alternate(struct odb_source *source,\n-\t\t\t\t\t    const char *alternate)\n-{\n-\tstruct lock_file lock = LOCK_INIT;\n-\tchar *path = xstrfmt(\"%s/%s\", source->path, \"info/alternates\");\n-\tFILE *in, *out;\n-\tint found = 0;\n-\tint ret;\n-\n-\trepo_hold_lock_file_for_update(source->odb->repo, &lock, path,\n-\t\t\t\t       LOCK_DIE_ON_ERROR);\n-\tout = fdopen_lock_file(&lock, \"w\");\n-\tif (!out) {\n-\t\tret = error_errno(_(\"unable to fdopen alternates lockfile\"));\n-\t\tgoto out;\n-\t}\n-\n-\tin = fopen(path, \"r\");\n-\tif (in) {\n-\t\tstruct strbuf line = STRBUF_INIT;\n-\n-\t\twhile (strbuf_getline(&line, in) != EOF) {\n-\t\t\tif (!strcmp(alternate, line.buf)) {\n-\t\t\t\tfound = 1;\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t\tfprintf_or_die(out, \"%s\\n\", line.buf);\n-\t\t}\n-\n-\t\tstrbuf_release(&line);\n-\t\tfclose(in);\n-\t} else if (errno != ENOENT) {\n-\t\tret = error_errno(_(\"unable to read alternates file\"));\n-\t\tgoto out;\n-\t}\n-\n-\tif (found) {\n-\t\trollback_lock_file(&lock);\n-\t} else {\n-\t\tfprintf_or_die(out, \"%s\\n\", alternate);\n-\t\tif (commit_lock_file(&lock)) {\n-\t\t\tret = error_errno(_(\"unable to move new alternates file into place\"));\n-\t\t\tgoto out;\n-\t\t}\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\tfree(path);\n-\treturn ret;\n-}\n-\n static int too_many_loose_objects(struct odb_source_files *files, int limit)\n {\n \tunsigned long loose_count;\n@@ -842,7 +789,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tfiles->base.write_object_stream = odb_source_files_write_object_stream;\n \tfiles->base.begin_transaction = odb_source_files_begin_transaction;\n \tfiles->base.read_alternates = odb_source_files_read_alternates;\n-\tfiles->base.write_alternate = odb_source_files_write_alternate;\n \tfiles->base.optimize = odb_source_files_optimize;\n \tfiles->base.optimize_required = odb_source_files_optimize_required;\n \ndiff --git a/odb/source-inmemory.c b/odb/source-inmemory.c\nindex 795672adf2..b00248dfb2 100644\n--- a/odb/source-inmemory.c\n+++ b/odb/source-inmemory.c\n@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t       const char *alternate UNUSED)\n-{\n-\treturn error(\"in-memory source does not support alternates\");\n-}\n-\n static void odb_source_inmemory_close(struct odb_source *source UNUSED)\n {\n }\n@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)\n \tsource->base.freshen_object = odb_source_inmemory_freshen_object;\n \tsource->base.begin_transaction = odb_source_inmemory_begin_transaction;\n \tsource->base.read_alternates = odb_source_inmemory_read_alternates;\n-\tsource->base.write_alternate = odb_source_inmemory_write_alternate;\n \n \treturn source;\n }\ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex bb3455dfbd..0f9b30bac1 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t    const char *alternate UNUSED)\n-{\n-\treturn error(\"loose source does not support alternates\");\n-}\n-\n static void odb_source_loose_clear_cache(struct odb_source_loose *loose)\n {\n \toidtree_clear(loose->cache);\n@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n \tloose->base.write_object_stream = odb_source_loose_write_object_stream;\n \tloose->base.begin_transaction = odb_source_loose_begin_transaction;\n \tloose->base.read_alternates = odb_source_loose_read_alternates;\n-\tloose->base.write_alternate = odb_source_loose_write_alternate;\n \n \tif (!is_absolute_path(loose->base.path))\n \t\tchdir_notify_register(NULL, odb_source_loose_reparent, loose);\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 630d955585..c2d253759c 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t     const char *alternate UNUSED)\n-{\n-\treturn error(\"packed backend cannot write alternates\");\n-}\n-\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n \tpacked->base.write_object_stream = odb_source_packed_write_object_stream;\n \tpacked->base.begin_transaction = odb_source_packed_begin_transaction;\n \tpacked->base.read_alternates = odb_source_packed_read_alternates;\n-\tpacked->base.write_alternate = odb_source_packed_write_alternate;\n \n \tif (!is_absolute_path(path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/odb/source.h b/odb/source.h\nindex 63f1c0c531..693a9fc604 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -286,19 +286,6 @@ struct odb_source {\n \tint (*read_alternates)(struct odb_source *source,\n \t\t\t       struct strvec *out);\n \n-\t/*\n-\t * This callback is expected to persist the singular alternate passed\n-\t * to it into its list of alternates. Any pre-existing alternates are\n-\t * expected to remain active. Subsequent calls to `read_alternates` are\n-\t * thus expected to yield the pre-existing list of alternates plus the\n-\t * newly added alternate appended to its end.\n-\t *\n-\t * The callback is expected to return 0 on success, a negative error\n-\t * code otherwise.\n-\t */\n-\tint (*write_alternate)(struct odb_source *source,\n-\t\t\t       const char *alternate);\n-\n \t/*\n \t * This callback is expected to optimize the object database source.\n \t * Returns 0 on success, a negative error code otherwise.\n@@ -518,19 +505,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,\n \treturn source->read_alternates(source, out);\n }\n \n-/*\n- * Write and persist a new alternate object database source for the given\n- * source. Any preexisting alternates are expected to stay valid, and the new\n- * alternate shall be appended to the end of the list.\n- *\n- * Returns 0 on success, a negative error code otherwise.\n- */\n-static inline int odb_source_write_alternate(struct odb_source *source,\n-\t\t\t\t\t      const char *alternate)\n-{\n-\treturn source->write_alternate(source, alternate);\n-}\n-\n /*\n  * Create a new transaction that can be used to write objects into a temporary\n  * staging area. The objects will only be persisted when the transaction is\n\n-- \n2.55.0.979.g7e5102b832.dirty\n\n"},{"id":"551623","messageId":"87se3twli4.fsf@emacs.iotcl.com","threadId":"66216","inReplyTo":"20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im","subject":"Re: [PATCH v2 0/8] odb: write alternates at creation time","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-09-01T06:09:39Z","receivedAt":"2026-09-01T06:09:59Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Hi,\n>\n> writing alternates into the object database currently happens via\n> `odb_source_write_alternate()`. But while that creates the ability to\n> create alternates at arbitrary points of a source's lifetime, we don't\n> use that functionality in the first place. Instead, we only ever write\n> alternates when creating a new repository.\n>\n> This design is suboptimal due to a couple of reasons:\n>\n>   - It requires us to have a `write_alternates()` callback, which is\n>     overblown as we never even write alternates to an object database\n>     after it has been created.\n>\n>   - We're about to make alternates an implementation detail of the\n>     object database's backend in a future patch series, so alternate\n>     implementations may not even support them.\n>\n>   - The backend has more flexibility with how exactly alternates are\n>     configured when it itself is in full control over their setup at the\n>     time where it creates the object database itself.\n>\n> This patch series thus refactors how we handle alternates so that we\n> don't write them ad-hoc anymore. Instead, the series introduces a new\n> option for `odb_source_create_on_disk()` that makes it handle those\n> alternates at creation time.\n>\n> This is part of the bigger goal of moving handling of alternates into\n> the \"files\" backend.\n>\n> This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)\n> with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop\n> `alternates_db` field, 2026-08-17) merged into it.\n>\n> Changes in v2:\n>   - Use a lockfile to write \"info/alternates\" during creation time.\n>   - Remove useless \"strvec.h\" include by reordering declarations a bit.\n>   - Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im\n\nThis version looks good to me!\n\n-- \nLaters,\nToon\n"},{"id":"552075","messageId":"ap2URrS4i-nHV5cB@denethor","threadId":"66216","inReplyTo":"20260831-pks-odb-write-alternates-at-creation-time-v2-1-aecd2382ba1c@pks.im","subject":"Re: [PATCH v2 1/8] builtin/clone: defer setup of the object database","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-09-06T16:42:48Z","receivedAt":"2026-09-06T16:42:53Z","isPatch":true,"body":"On 26/08/31 12:02PM, Patrick Steinhardt wrote:\n> When cloning a repository we defer initialization of the reference\n> database. This is because we don't yet know all details required for us\n> to initialize the refdb in the first place. Most importantly, what we\n> are missing is information about the object hash.\n> \n> We don't do the same thing for the object database yet, but here we\n> essentially have the same problem. While the \"files\" database does not\n> need any information about the object format at creation time, alternate\n> backends are likely to require that information so that they can\n> properly set up their data structures.\n\nOk, so if we want to move the writing of alternates to happen at ODB\ncreation time, this information will be needed ahead of time.\n\n> Besides this forward-looking future proofing though, we also have a\n> second use case for deferring initialization of the object database,\n> namely alternates. When initializing the object database we do not yet\n> know whether we'll need alternates or not because this depends on the\n> repository we're about to clone from. If it is a local repository and\n> the user has passed \"--refernce{,-if-able}\", then we will end up writing\n> alternates into the object database.\n> \n> The ugly part though is that we cannot determine where the repository is\n> getting cloned from before it has been initialized. While we of course\n> already have access to the user-provided URI, that URI can be very well\n> rewritten via \"url.<base>.insteadOf\". We can of course read the global-\n> and system-level configuration to resolve it. But we explicitly resolve\n> the URI a second time after we have initialized the repository because\n> it can happen that we copy a \".git/config\" over from our templates, and\n> that file may cause us to rewrite the path.\n> \n> In a subsequent commit though we'll start to write alternates as part of\n> the repository initialization, so we'll need to have the URI properly\n> resolved before we can initialize the object database. This is ugly, but\n> as mentioned above it makes sense for us to defer its initialization\n> anyway so that we also know about the object hash already.\n\nOk.\n\n> Introduce a new flag that makes `init_db()` skip initializing the object\n> database. Expose `create_object_database()` and make use of it after we\n> have resolved the URI.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/clone.c | 12 ++++++++----\n>  setup.c         |  8 +++++---\n>  setup.h         | 15 ++++++++++-----\n>  3 files changed, 23 insertions(+), 12 deletions(-)\n> \n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index 5b25cca510..0a67492ebd 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -1184,11 +1184,14 @@ int cmd_clone(int argc,\n>  \t * database. We do not yet know about the object format of the\n>  \t * repository, and reference backends may persist that information into\n>  \t * their on-disk data structures.\n> +\t *\n> +\t * Furthermore, we skip initializing the object database so that we can\n> +\t * first resolve potential alternates before creating it.\n>  \t */\n>  \tinit_db(the_repository, git_dir, real_git_dir, work_tree, option_template,\n>  \t\tGIT_HASH_UNKNOWN, ref_storage_format, NULL,\n>  \t\tdo_not_override_repo_unix_permissions,\n> -\t\tINIT_DB_QUIET | INIT_DB_SKIP_REFDB);\n> +\t\tINIT_DB_QUIET | INIT_DB_SKIP_REFDB | INIT_DB_SKIP_ODB);\n\nOk, now we skip the initializing the ODB during init_db in favor of\ndelaying it to after we have the required config info. This makes sense\nto me, but IMO the `init_db()` interface has grown quite awkward with\nthese \"skip\" flags. It appears that there are only two callers of\n`init_db()` which makes me wonder if it would be simpler to just require\nthem to explicitly set up the ref DB and ODB.\n\n>  \tif (real_git_dir) {\n>  \t\tfree((char *)git_dir);\n> @@ -1311,9 +1314,6 @@ int cmd_clone(int argc,\n>  \t\tstrbuf_reset(&key);\n>  \t}\n>  \n> -\tif (option_required_reference.nr || option_optional_reference.nr)\n> -\t\tsetup_reference();\n> -\n>  \tremote = remote_get_early(remote_name);\n>  \n>  \tif (!option_rev)\n> @@ -1342,6 +1342,10 @@ int cmd_clone(int argc,\n>  \tif (option_local > 0 && !is_local)\n>  \t\twarning(_(\"--local is ignored\"));\n>  \n> +\tcreate_object_database(the_repository);\n\nWe now explicitly create the object database here.\n\n> +\tif (option_required_reference.nr || option_optional_reference.nr)\n> +\t\tsetup_reference();\n\nAny reason the reference setup is also further deferred here?\n\n-Justin\n"},{"id":"552078","messageId":"ap2bu85Ut_bN4wkt@denethor","threadId":"66216","inReplyTo":"20260831-pks-odb-write-alternates-at-creation-time-v2-6-aecd2382ba1c@pks.im","subject":"Re: [PATCH v2 6/8] odb/source: support writing alternates when creating the database","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-09-06T17:02:57Z","receivedAt":"2026-09-06T17:03:02Z","isPatch":true,"body":"On 26/08/31 12:02PM, Patrick Steinhardt wrote:\n> +struct odb_create_on_disk_options {\n> +\t/*\n> +\t * Alternates that shall be written into the newly created object\n> +\t * database. Whether or not this option can be handled is specific to\n> +\t * the backend.\n> +\t */\n> +\tconst struct strvec *alternates;\n> +};\n\nSo ODB interface here still exposes a way to signal alternates to the\nbackend, but an alternative backend may not actually use alternates at\nall. Out of curiousity, does this mean git-clone(1) options such as\n\"--shared\" should only be allowed to work with the \"files\" backend? Or\nshould the expected behavior of such options just be backend specific?\n\n-Justin\n"},{"id":"552079","messageId":"ap2c8QObeMWlGSd2@denethor","threadId":"66216","inReplyTo":"20260831-pks-odb-write-alternates-at-creation-time-v2-7-aecd2382ba1c@pks.im","subject":"Re: [PATCH v2 7/8] builtin/clone: write alternates via `odb_create_on_disk()`","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-09-06T17:07:01Z","receivedAt":"2026-09-06T17:07:03Z","isPatch":true,"body":"On 26/08/31 12:02PM, Patrick Steinhardt wrote:\n> When creating a repository with alternates we first initialize the\n> object database and then write alternates to it in a separate step. This\n> is unfortunate due to a couple of reasons:\n> \n>   - It requires us to have a `write_alternates()` callback, which is\n>     unfortunate as we never even write alternates to an object database\n>     after it has been created.\n\nHappy to see the list of callbacks shrink. :)\n\n>   - We're about to make alternates an implementation detail of the\n>     object database's backend in a future patch series, so having this\n>     callback is suboptimal there.\n> \n>   - The backend has more flexibility with how exactly alternates are\n>     configured when it itself is in full control over their setup at the\n>     time where it creates the object database itself.\n> \n> We have thus introduced the ability to write alternates right at\n> creation time in the preceding commits, and we have unified setup of\n> alternates into a single location. All that's left to do for us now is\n> to wire up alternates as an option for the database creation.\n\nMakes sense.\n\n> Do so.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/clone.c | 5 +----\n>  setup.c         | 9 ++++++---\n>  setup.h         | 2 +-\n>  3 files changed, 8 insertions(+), 8 deletions(-)\n> \n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index 2e3473fddf..48ac379b1d 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -1368,11 +1368,8 @@ int cmd_clone(int argc,\n>  \tif (option_local > 0 && !is_local)\n>  \t\twarning(_(\"--local is ignored\"));\n>  \n> -\tcreate_object_database(the_repository);\n>  \tcollect_alternates(&alternates, path, is_local);\n> -\n> -\tfor (size_t i = 0; i < alternates.nr; i++)\n> -\t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n> +\tcreate_object_database(the_repository, &alternates);\n\nAlternates are no longer explicitly set up here and instead wired and\nhandled via `create_object_database()`. This patch looks good.\n\n-Justin\n"},{"id":"552095","messageId":"ap5mafyJrnXCTw_L@pks.im","threadId":"66216","inReplyTo":"ap2URrS4i-nHV5cB@denethor","subject":"Re: [PATCH v2 1/8] builtin/clone: defer setup of the object database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-07T07:23:21Z","receivedAt":"2026-09-07T07:23:33Z","isPatch":true,"body":"On Sun, Sep 06, 2026 at 11:42:48AM -0500, Justin Tobler wrote:\n> On 26/08/31 12:02PM, Patrick Steinhardt wrote:\n> > diff --git a/builtin/clone.c b/builtin/clone.c\n> > index 5b25cca510..0a67492ebd 100644\n> > --- a/builtin/clone.c\n> > +++ b/builtin/clone.c\n> > @@ -1184,11 +1184,14 @@ int cmd_clone(int argc,\n> >  \t * database. We do not yet know about the object format of the\n> >  \t * repository, and reference backends may persist that information into\n> >  \t * their on-disk data structures.\n> > +\t *\n> > +\t * Furthermore, we skip initializing the object database so that we can\n> > +\t * first resolve potential alternates before creating it.\n> >  \t */\n> >  \tinit_db(the_repository, git_dir, real_git_dir, work_tree, option_template,\n> >  \t\tGIT_HASH_UNKNOWN, ref_storage_format, NULL,\n> >  \t\tdo_not_override_repo_unix_permissions,\n> > -\t\tINIT_DB_QUIET | INIT_DB_SKIP_REFDB);\n> > +\t\tINIT_DB_QUIET | INIT_DB_SKIP_REFDB | INIT_DB_SKIP_ODB);\n> \n> Ok, now we skip the initializing the ODB during init_db in favor of\n> delaying it to after we have the required config info. This makes sense\n> to me, but IMO the `init_db()` interface has grown quite awkward with\n> these \"skip\" flags. It appears that there are only two callers of\n> `init_db()` which makes me wonder if it would be simpler to just require\n> them to explicitly set up the ref DB and ODB.\n\nWe can, but there are some nuances here that make this a bit more\ncomplicated. Most importantly, we'd start printing the message that the\nrepository was (re)initialized _before_ we create the actual object and\nreference databases.\n\nBut thinking about this a bit more we can solve this, and we can even\nget rid of the flags completely here:\n\n  - git-clone(1) always passes the QUIET flag, so we really only want to\n    print the message for git-init(1) anyway. So we can lift the logic\n    out of `init_db()` and then drop the flag.\n\n  - Same for `EXIST_OK`, we never allow preexisting repositories when\n    performing a clone.\n\nThe name `init_db()` would become very misleading in that case though,\nso we should probably rename it to e.g. `create_repository()`. But\noverall the change makes sense, as it moves the command-specific logic\nto the commands themselves instead of making use of flags to control it.\nI like it.\n\n> > @@ -1311,9 +1314,6 @@ int cmd_clone(int argc,\n> >  \t\tstrbuf_reset(&key);\n> >  \t}\n> >  \n> > -\tif (option_required_reference.nr || option_optional_reference.nr)\n> > -\t\tsetup_reference();\n> > -\n> >  \tremote = remote_get_early(remote_name);\n> >  \n> >  \tif (!option_rev)\n> > @@ -1342,6 +1342,10 @@ int cmd_clone(int argc,\n> >  \tif (option_local > 0 && !is_local)\n> >  \t\twarning(_(\"--local is ignored\"));\n> >  \n> > +\tcreate_object_database(the_repository);\n> \n> We now explicitly create the object database here.\n> \n> > +\tif (option_required_reference.nr || option_optional_reference.nr)\n> > +\t\tsetup_reference();\n> \n> Any reason the reference setup is also further deferred here?\n\nYou might think that this has something to do with refs (\"refs/*\"), but\nthat's not the case. This setup here sets up alternates, and we can only\nset those up after we have created the object database. I'll note this\nin the commit message as it's quite non-obvious.\n\nThanks!\n\nPatrick\n"},{"id":"552096","messageId":"ap5medBVRZbjI0gM@pks.im","threadId":"66216","inReplyTo":"ap2bu85Ut_bN4wkt@denethor","subject":"Re: [PATCH v2 6/8] odb/source: support writing alternates when creating the database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-07T07:23:37Z","receivedAt":"2026-09-07T07:23:42Z","isPatch":true,"body":"On Sun, Sep 06, 2026 at 12:02:57PM -0500, Justin Tobler wrote:\n> On 26/08/31 12:02PM, Patrick Steinhardt wrote:\n> > +struct odb_create_on_disk_options {\n> > +\t/*\n> > +\t * Alternates that shall be written into the newly created object\n> > +\t * database. Whether or not this option can be handled is specific to\n> > +\t * the backend.\n> > +\t */\n> > +\tconst struct strvec *alternates;\n> > +};\n> \n> So ODB interface here still exposes a way to signal alternates to the\n> backend, but an alternative backend may not actually use alternates at\n> all. Out of curiousity, does this mean git-clone(1) options such as\n> \"--shared\" should only be allowed to work with the \"files\" backend? Or\n> should the expected behavior of such options just be backend specific?\n\nThe latter. It's totally feasible that an alternative backend may\nsupport alternates, but it's also feasible that it doesn't.\n\nPatrick\n"},{"id":"552105","messageId":"20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im","subject":"[PATCH v3 0/9] odb: write alternates at creation time","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-07T08:25:37Z","receivedAt":"2026-09-07T08:25:49Z","isPatch":true,"body":"Hi,\n\nwriting alternates into the object database currently happens via\n`odb_source_write_alternate()`. But while that creates the ability to\ncreate alternates at arbitrary points of a source's lifetime, we don't\nuse that functionality in the first place. Instead, we only ever write\nalternates when creating a new repository.\n\nThis design is suboptimal due to a couple of reasons:\n\n  - It requires us to have a `write_alternates()` callback, which is\n    overblown as we never even write alternates to an object database\n    after it has been created.\n\n  - We're about to make alternates an implementation detail of the\n    object database's backend in a future patch series, so alternate\n    implementations may not even support them.\n\n  - The backend has more flexibility with how exactly alternates are\n    configured when it itself is in full control over their setup at the\n    time where it creates the object database itself.\n\nThis patch series thus refactors how we handle alternates so that we\ndon't write them ad-hoc anymore. Instead, the series introduces a new\noption for `odb_source_create_on_disk()` that makes it handle those\nalternates at creation time.\n\nThis is part of the bigger goal of moving handling of alternates into\nthe \"files\" backend.\n\nThis series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)\nwith ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop\n`alternates_db` field, 2026-08-17) merged into it.\n\nChanges in v3:\n  - Refactor `init_db()` to not create the reference and object database\n    at all anymore. Instead, it's now called `create_repository()` and\n    it is responsible for creating the initial repository skeleton,\n    only. This allows us to get rid of the flags and overall makes the\n    logic more straight-forward by moving the command-specific logic\n    into the respective commands.\n  - A couple of typo fixes.\n  - Link to v2: https://patch.msgid.link/20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im\n\nChanges in v2:\n  - Use a lockfile to write \"info/alternates\" during creation time.\n  - Remove useless \"strvec.h\" include by reordering declarations a bit.\n  - Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (9):\n      setup: split up concerns of `init_db()`\n      builtin/clone: defer setup of the object database\n      builtin/clone: move around `setup_reference()`\n      builtin/clone: refactor handling of \"--reference{,-if-able}\"\n      builtin/clone: move setup of alternates for shared local clones\n      builtin/clone: move setup of alternates for non-shared local clones\n      odb/source: support writing alternates when creating the database\n      builtin/clone: write alternates via `odb_create_on_disk()`\n      odb/source: remove the ability to write alternates\n\n builtin/clone.c            | 111 +++++++++++++++++++++++---------------\n builtin/init-db.c          |  32 ++++++++---\n odb.c                      |   9 ----\n odb.h                      |   7 ---\n odb/source-files.c         | 130 ++++++++++++++++++++++++++-------------------\n odb/source-inmemory.c      |   7 ---\n odb/source-loose.c         |   7 ---\n odb/source-packed.c        |   7 ---\n odb/source.h               |  43 +++++----------\n setup.c                    |  61 ++++++++-------------\n setup.h                    |  22 ++++----\n t/t5604-clone-reference.sh |  25 +++++++++\n 12 files changed, 239 insertions(+), 222 deletions(-)\n\nRange-diff versus v2:\n\n 1:  1a1580185a <  -:  ---------- builtin/clone: defer setup of the object database\n -:  ---------- >  1:  512e7d1ef2 setup: split up concerns of `init_db()`\n -:  ---------- >  2:  a8808bcacf builtin/clone: defer setup of the object database\n 2:  7d0d4832ed =  3:  0193fb3ada builtin/clone: move around `setup_reference()`\n 3:  d547e62b5b !  4:  3c53ad13f2 builtin/clone: refactor handling of \"--reference{,-if-able}\"\n    @@ Commit message\n         In subsequent commits we're about to consolidate the complete setup of\n         alternates into this function, and furthermore we'll refactor the setup\n         of the object database to handle doing this for us instead of writing\n    -    the alterantes into it one by one.\n    +    the alternates into it one by one.\n     \n         Prepare for this refactoring by collecting the alternates into a strvec.\n         Rename the function to `collect_alternates()` to clarify its scope.\n 4:  ceb90593c8 =  5:  4fbcab0026 builtin/clone: move setup of alternates for shared local clones\n 5:  d9587585f3 !  6:  b658ca0799 builtin/clone: move setup of alternates for non-shared local clones\n    @@ Commit message\n         purpose.\n     \n         Add two tests, the first one of which exercises this bug to avoid future\n    -    regressions. The second patch ensures that we properly handle relative\n    +    regressions. The second test ensures that we properly handle relative\n         alternates for a referenced worktree.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n 6:  7575379b17 =  7:  bec8115803 odb/source: support writing alternates when creating the database\n 7:  c07fd20d85 !  8:  3deaa7ef64 builtin/clone: write alternates via `odb_create_on_disk()`\n    @@ builtin/clone.c: int cmd_clone(int argc,\n      \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n      \ttransport_set_verbosity(transport, option_verbosity, option_progress);\n     \n    + ## builtin/init-db.c ##\n    +@@ builtin/init-db.c: int cmd_init_db(int argc,\n    + \t\t\t  template_dir, hash_algo, ref_storage_format,\n    + \t\t\t  init_shared_repository, &reinit);\n    + \tcreate_reference_database(the_repository, initial_branch, quiet);\n    +-\tcreate_object_database(the_repository);\n    ++\tcreate_object_database(the_repository, NULL);\n    + \n    + \tif (!quiet) {\n    + \t\tint len = strlen(git_dir);\n    +\n      ## setup.c ##\n     @@ setup.c: static int create_default_files(struct repository *repo,\n      \treturn reinit;\n    @@ setup.c: static int create_default_files(struct repository *repo,\n      \n      \t/*\n      \t * Create the \"objects\" directory in the common directory. This is done\n    -@@ setup.c: int init_db(struct repository *repo,\n    - \tif (!(flags & INIT_DB_SKIP_REFDB))\n    - \t\tcreate_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);\n    - \tif (!(flags & INIT_DB_SKIP_ODB))\n    --\t\tcreate_object_database(repo);\n    -+\t\tcreate_object_database(repo, NULL);\n    - \n    - \tstartup_info->have_repository = 1;\n    - \n     \n      ## setup.h ##\n     @@ setup.h: void initialize_repository_version(struct repository *repo,\n 8:  a8b9f51897 =  9:  d751b886b3 odb/source: remove the ability to write alternates\n\n---\nbase-commit: afa255aeb620346d56a2c01fb5ae9163513c56d7\nchange-id: 20260813-pks-odb-write-alternates-at-creation-time-64010deb94a0\n\n"},{"id":"552106","messageId":"20260907-pks-odb-write-alternates-at-creation-time-v3-1-735d0b5b3e00@pks.im","threadId":"66216","inReplyTo":"20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im","subject":"[PATCH v3 1/9] setup: split up concerns of `init_db()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-07T08:25:38Z","receivedAt":"2026-09-07T08:25:51Z","isPatch":true,"body":"The function `init_db()` is responsible for creating the on-disk\ndirectory structure required for a Git repository. It is used by both\ngit-init(1) and git-clone(1), and because their expected behaviour is\ndifferent we support a couple of flags:\n\n  - The `QUIET` flag controls whether the command is quiet or not. For\n    git-init(1) this is user-controllable, whereas for git-clone(1)\n    we're always quiet.\n\n  - The `EXIST_OK` flag controls whether a preexisting repository is\n    okay or not. For git-init(1) it is, for git-clone(1) it's not.\n\n  - The `SKIP_REFDB` flag controls whether the reference database should\n    already be created or not. For git-init(1) we do, but for\n    git-clone(1) we don't because it does not yet know about the default\n    branch and about the remote object hash.\n\nFurthermore, we're about to add another divergence in behaviour, where\nwe have to also skip creation of the object database in git-clone(1).\nThis is becoming quite cumbersome though.\n\nInstead of introducing another flag, start to split up concerns of the\nfunction so that we never create the reference or object database. This\nbecomes the responsibility of the caller, which is thus free to defer\ntheir creation to a later point in time. This lets us get rid of most of\nthe divergent behaviour:\n\n  - We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags\n    anymore.\n\n  - We don't need the `QUIET` flag anymore, as nothing prints output\n    except for the final status message that tells the user that the\n    repository has been (re)initialized. But as this message is specific\n    to git-init(1), we can easily move it there.\n\nThe only piece of information we still have to convey is whether or not\nreinitialization of a preexisting repository is okay. This is handled by\na new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay\nto reinitialize the repository. Furthermore, the pointer will be written\nto to indicate whether the repository was reinitialized or not, which we\nneed in git-init(1) to print the correct initialization message.\n\nWith these refactorings, `init_db()` is named quite misleadingly though,\nas we don't create any of the reference or object databases anymore.\nRename it to `create_repository()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c   |  9 +++++----\n builtin/init-db.c | 32 ++++++++++++++++++++++++--------\n setup.c           | 54 +++++++++++++++++-------------------------------------\n setup.h           | 22 ++++++++++------------\n 4 files changed, 56 insertions(+), 61 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 5b25cca510..904d2d859f 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1185,10 +1185,10 @@ int cmd_clone(int argc,\n \t * repository, and reference backends may persist that information into\n \t * their on-disk data structures.\n \t */\n-\tinit_db(the_repository, git_dir, real_git_dir, work_tree, option_template,\n-\t\tGIT_HASH_UNKNOWN, ref_storage_format, NULL,\n-\t\tdo_not_override_repo_unix_permissions,\n-\t\tINIT_DB_QUIET | INIT_DB_SKIP_REFDB);\n+\tcreate_repository(the_repository, git_dir, real_git_dir, work_tree,\n+\t\t\t  option_template, GIT_HASH_UNKNOWN, ref_storage_format,\n+\t\t\t  do_not_override_repo_unix_permissions, NULL);\n+\tcreate_object_database(the_repository);\n \n \tif (real_git_dir) {\n \t\tfree((char *)git_dir);\n@@ -1445,6 +1445,7 @@ int cmd_clone(int argc,\n \tinitialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);\n \trepo_set_hash_algo(the_repository, hash_algo);\n \tcreate_reference_database(the_repository, NULL, 1);\n+\tstartup_info->have_repository = 1;\n \n \t/*\n \t * Before fetching from the remote, download and install bundle\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex e96b1283b7..f2c7e3be6d 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -80,7 +80,7 @@ int cmd_init_db(int argc,\n \tchar *work_tree = NULL;\n \tconst char *template_dir = NULL;\n \tchar *template_dir_to_free = NULL;\n-\tunsigned int flags = 0;\n+\tint quiet = 0;\n \tint bare = startup_info->force_bare_repository ? 1 : -1;\n \tconst char *object_format = NULL;\n \tconst char *ref_format = NULL;\n@@ -102,7 +102,7 @@ int cmd_init_db(int argc,\n \t\t\t.flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG,\n \t\t\t.callback = shared_callback\n \t\t},\n-\t\tOPT_BIT('q', \"quiet\", &flags, N_(\"be quiet\"), INIT_DB_QUIET),\n+\t\tOPT_BOOL('q', \"quiet\", &quiet, N_(\"be quiet\")),\n \t\tOPT_STRING(0, \"separate-git-dir\", &real_git_dir, N_(\"gitdir\"),\n \t\t\t   N_(\"separate git dir from working tree\")),\n \t\tOPT_STRING('b', \"initial-branch\", &initial_branch, N_(\"name\"),\n@@ -113,7 +113,7 @@ int cmd_init_db(int argc,\n \t\t\t   N_(\"specify the reference format to use\")),\n \t\tOPT_END()\n \t};\n-\tint ret;\n+\tint reinit;\n \n \targc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);\n \n@@ -247,14 +247,30 @@ int cmd_init_db(int argc,\n \t\tdie(_(\"--separate-git-dir incompatible with bare repository\"));\n \t}\n \n-\tflags |= INIT_DB_EXIST_OK;\n-\tret = init_db(the_repository, git_dir, real_git_dir, work_tree,\n-\t\t      template_dir, hash_algo, ref_storage_format, initial_branch,\n-\t\t      init_shared_repository, flags);\n+\tcreate_repository(the_repository, git_dir, real_git_dir, work_tree,\n+\t\t\t  template_dir, hash_algo, ref_storage_format,\n+\t\t\t  init_shared_repository, &reinit);\n+\tcreate_reference_database(the_repository, initial_branch, quiet);\n+\tcreate_object_database(the_repository);\n+\n+\tif (!quiet) {\n+\t\tint len = strlen(git_dir);\n+\n+\t\tif (reinit)\n+\t\t\tprintf(repo_settings_get_shared_repository(the_repository)\n+\t\t\t       ? _(\"Reinitialized existing shared Git repository in %s%s\\n\")\n+\t\t\t       : _(\"Reinitialized existing Git repository in %s%s\\n\"),\n+\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n+\t\telse\n+\t\t\tprintf(repo_settings_get_shared_repository(the_repository)\n+\t\t\t       ? _(\"Initialized empty shared Git repository in %s%s\\n\")\n+\t\t\t       : _(\"Initialized empty Git repository in %s%s\\n\"),\n+\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n+\t}\n \n \tfree(template_dir_to_free);\n \tfree(real_git_dir_to_free);\n \tfree(work_tree);\n \tfree(git_dir);\n-\treturn ret;\n+\treturn 0;\n }\ndiff --git a/setup.c b/setup.c\nindex d90654f584..8c7b97f92e 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,\n \treturn reinit;\n }\n \n-static void create_object_database(struct repository *repo)\n+void create_object_database(struct repository *repo)\n {\n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\n@@ -2822,17 +2822,17 @@ static void repository_format_configure(struct repository_format *repo_fmt,\n \t}\n }\n \n-int init_db(struct repository *repo,\n-\t    const char *git_dir,\n-\t    const char *real_git_dir,\n-\t    const char *worktree,\n-\t    const char *template_dir, int hash,\n-\t    enum ref_storage_format ref_storage_format,\n-\t    const char *initial_branch,\n-\t    int init_shared_repository, unsigned int flags)\n+void create_repository(struct repository *repo,\n+\t\t       const char *git_dir,\n+\t\t       const char *real_git_dir,\n+\t\t       const char *worktree,\n+\t\t       const char *template_dir,\n+\t\t       int hash,\n+\t\t       enum ref_storage_format ref_storage_format,\n+\t\t       int init_shared_repository,\n+\t\t       int *reinit_ok)\n {\n-\tint reinit;\n-\tint exist_ok = flags & INIT_DB_EXIST_OK;\n+\tint reinit_ignored;\n \tchar *original_git_dir = real_pathdup(git_dir, 1);\n \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2840,10 +2840,10 @@ int init_db(struct repository *repo,\n \tif (real_git_dir) {\n \t\tstruct stat st;\n \n-\t\tif (!exist_ok && !stat(git_dir, &st))\n+\t\tif (!reinit_ok && !stat(git_dir, &st))\n \t\t\tdie(_(\"%s already exists\"), git_dir);\n \n-\t\tif (!exist_ok && !stat(real_git_dir, &st))\n+\t\tif (!reinit_ok && !stat(real_git_dir, &st))\n \t\t\tdie(_(\"%s already exists\"), real_git_dir);\n \n \t\tapply_and_export_relative_gitdir(repo, real_git_dir, 1);\n@@ -2877,8 +2877,10 @@ int init_db(struct repository *repo,\n \n \tsafe_create_dir(repo, git_dir, 0);\n \n-\treinit = create_default_files(repo, template_dir, original_git_dir,\n-\t\t\t\t      &repo_fmt, init_shared_repository);\n+\tif (!reinit_ok)\n+\t\treinit_ok = &reinit_ignored;\n+\t*reinit_ok = create_default_files(repo, template_dir, original_git_dir,\n+\t\t\t\t\t  &repo_fmt, init_shared_repository);\n \n \tif (repo_settings_get_shared_repository(repo)) {\n \t\tchar buf[10];\n@@ -2901,29 +2903,7 @@ int init_db(struct repository *repo,\n \t\trepo_config_set(repo, \"receive.denyNonFastforwards\", \"true\");\n \t}\n \n-\tif (!(flags & INIT_DB_SKIP_REFDB))\n-\t\tcreate_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);\n-\tcreate_object_database(repo);\n-\n-\tstartup_info->have_repository = 1;\n-\n-\tif (!(flags & INIT_DB_QUIET)) {\n-\t\tint len = strlen(git_dir);\n-\n-\t\tif (reinit)\n-\t\t\tprintf(repo_settings_get_shared_repository(repo)\n-\t\t\t       ? _(\"Reinitialized existing shared Git repository in %s%s\\n\")\n-\t\t\t       : _(\"Reinitialized existing Git repository in %s%s\\n\"),\n-\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n-\t\telse\n-\t\t\tprintf(repo_settings_get_shared_repository(repo)\n-\t\t\t       ? _(\"Initialized empty shared Git repository in %s%s\\n\")\n-\t\t\t       : _(\"Initialized empty Git repository in %s%s\\n\"),\n-\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n-\t}\n-\n \tclear_repository_format(&repo_fmt);\n \tstrbuf_release(&err);\n \tfree(original_git_dir);\n-\treturn 0;\n }\ndiff --git a/setup.h b/setup.h\nindex 763fd384e8..c4aa464caa 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -256,23 +256,21 @@ int apply_repository_format(struct repository *repo,\n \n const char *get_template_dir(const char *option_template);\n \n-#define INIT_DB_QUIET      (1 << 0)\n-#define INIT_DB_EXIST_OK   (1 << 1)\n-#define INIT_DB_SKIP_REFDB (1 << 2)\n-\n-int init_db(struct repository *repo,\n-\t    const char *git_dir,\n-\t    const char *real_git_dir,\n-\t    const char *worktree,\n-\t    const char *template_dir, int hash_algo,\n-\t    enum ref_storage_format ref_storage_format,\n-\t    const char *initial_branch, int init_shared_repository,\n-\t    unsigned int flags);\n+void create_repository(struct repository *repo,\n+\t\t       const char *git_dir,\n+\t\t       const char *real_git_dir,\n+\t\t       const char *worktree,\n+\t\t       const char *template_dir,\n+\t\t       int hash_algo,\n+\t\t       enum ref_storage_format ref_storage_format,\n+\t\t       int init_shared_repository,\n+\t\t       int *reinit_ok);\n void initialize_repository_version(struct repository *repo,\n \t\t\t\t   int hash_algo,\n \t\t\t\t   enum ref_storage_format ref_storage_format,\n \t\t\t\t   int reinit);\n void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);\n+void create_object_database(struct repository *repo);\n \n /*\n  * NOTE NOTE NOTE!!\n\n-- \n2.55.0.1007.g17ff1f9808.dirty\n\n"},{"id":"552107","messageId":"20260907-pks-odb-write-alternates-at-creation-time-v3-2-735d0b5b3e00@pks.im","threadId":"66216","inReplyTo":"20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im","subject":"[PATCH v3 2/9] builtin/clone: defer setup of the object database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-07T08:25:39Z","receivedAt":"2026-09-07T08:25:54Z","isPatch":true,"body":"When cloning a repository we defer initialization of the reference\ndatabase. This is because we don't yet know all details required for us\nto initialize the refdb in the first place. Most importantly, what we\nare missing is information about the object hash.\n\nWe don't do the same thing for the object database yet, but here we\nessentially have the same problem. While the \"files\" database does not\nneed any information about the object format at creation time, alternate\nbackends are likely to require that information so that they can\nproperly set up their data structures.\n\nBesides this forward-looking future proofing though, we also have a\nsecond use case for deferring initialization of the object database,\nnamely alternates. When initializing the object database we do not yet\nknow whether we'll need alternates or not because this depends on the\nrepository we're about to clone from. If it is a local repository and\nthe user has passed \"--refernce{,-if-able}\", then we will end up writing\nalternates into the object database.\n\nThe ugly part though is that we cannot determine where the repository is\ngetting cloned from before it has been initialized. While we of course\nalready have access to the user-provided URI, that URI can be very well\nrewritten via \"url.<base>.insteadOf\". We can of course read the global-\nand system-level configuration to resolve it. But we explicitly resolve\nthe URI a second time after we have initialized the repository because\nit can happen that we copy a \".git/config\" over from our templates, and\nthat file may cause us to rewrite the path.\n\nIn a subsequent commit though we'll start to write alternates as part of\nthe repository initialization, so we'll need to have the URI properly\nresolved before we can initialize the object database. This is ugly, but\nas mentioned above it makes sense for us to defer its initialization\nanyway so that we also know about the object hash already.\n\nDefer creation of the object database until after we have resolved the\nURI.\n\nNote that this also requires us to defer the call to `setup_reference()`\nuntil after we have created the object database. While you might think\nthat this function has something to do with references (\"refs/*\"), it is\nin fact responsible for setting up alternates. Consequently, we can only\ncall it after we have created the object database already.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 904d2d859f..bdcbd7aa1b 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1188,7 +1188,6 @@ int cmd_clone(int argc,\n \tcreate_repository(the_repository, git_dir, real_git_dir, work_tree,\n \t\t\t  option_template, GIT_HASH_UNKNOWN, ref_storage_format,\n \t\t\t  do_not_override_repo_unix_permissions, NULL);\n-\tcreate_object_database(the_repository);\n \n \tif (real_git_dir) {\n \t\tfree((char *)git_dir);\n@@ -1311,9 +1310,6 @@ int cmd_clone(int argc,\n \t\tstrbuf_reset(&key);\n \t}\n \n-\tif (option_required_reference.nr || option_optional_reference.nr)\n-\t\tsetup_reference();\n-\n \tremote = remote_get_early(remote_name);\n \n \tif (!option_rev)\n@@ -1342,6 +1338,10 @@ int cmd_clone(int argc,\n \tif (option_local > 0 && !is_local)\n \t\twarning(_(\"--local is ignored\"));\n \n+\tcreate_object_database(the_repository);\n+\tif (option_required_reference.nr || option_optional_reference.nr)\n+\t\tsetup_reference();\n+\n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\n \ttransport->family = family;\n\n-- \n2.55.0.1007.g17ff1f9808.dirty\n\n"},{"id":"552108","messageId":"20260907-pks-odb-write-alternates-at-creation-time-v3-3-735d0b5b3e00@pks.im","threadId":"66216","inReplyTo":"20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im","subject":"[PATCH v3 3/9] builtin/clone: move around `setup_reference()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-07T08:25:40Z","receivedAt":"2026-09-07T08:25:56Z","isPatch":true,"body":"In a subsequent commit, `setup_reference()` will start to call\n`copy_alternates()`. Prepare for this by moving the function further\ndown so that we can avoid adding a declaration.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex bdcbd7aa1b..ac5843d7b9 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)\n \treturn 0;\n }\n \n-static void setup_reference(void)\n-{\n-\tint required = 1;\n-\tfor_each_string_list(&option_required_reference,\n-\t\t\t     add_one_reference, &required);\n-\trequired = 0;\n-\tfor_each_string_list(&option_optional_reference,\n-\t\t\t     add_one_reference, &required);\n-}\n-\n static void copy_alternates(struct strbuf *src, const char *src_repo)\n {\n \t/*\n@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n+static void setup_reference(void)\n+{\n+\tint required = 1;\n+\tfor_each_string_list(&option_required_reference,\n+\t\t\t     add_one_reference, &required);\n+\trequired = 0;\n+\tfor_each_string_list(&option_optional_reference,\n+\t\t\t     add_one_reference, &required);\n+}\n+\n static void mkdir_if_missing(const char *pathname, mode_t mode)\n {\n \tstruct stat st;\n\n-- \n2.55.0.1007.g17ff1f9808.dirty\n\n"},{"id":"552109","messageId":"20260907-pks-odb-write-alternates-at-creation-time-v3-4-735d0b5b3e00@pks.im","threadId":"66216","inReplyTo":"20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im","subject":"[PATCH v3 4/9] builtin/clone: refactor handling of \"--reference{,-if-able}\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-07T08:25:41Z","receivedAt":"2026-09-07T08:25:58Z","isPatch":true,"body":"Users can pass \"--reference{,-if-able}\" to git-clone(1) to instruct it\nto set up alternates for the newly created repository. This allows it to\nreuse objects from the source repository so that in the best case we\ndon't have to clone all objects over.\n\nThose options are handled by the confusingly named `setup_reference()`\nfunction -- without the above context, one might rightfully believe that\nit was about refs, not about alternates. The function itself is rather\nsimple: we loop through all provided alternate paths and then, if such\nan alternate is valid, we write it to the object database.\n\nIn subsequent commits we're about to consolidate the complete setup of\nalternates into this function, and furthermore we'll refactor the setup\nof the object database to handle doing this for us instead of writing\nthe alternates into it one by one.\n\nPrepare for this refactoring by collecting the alternates into a strvec.\nRename the function to `collect_alternates()` to clarify its scope.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------\n 1 file changed, 28 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex ac5843d7b9..8786a49332 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)\n \treturn canon;\n }\n \n-static int add_one_reference(struct string_list_item *item, void *cb_data)\n+struct add_one_alternate_data {\n+\tstruct strvec *alternates;\n+\tint required;\n+};\n+\n+static int add_one_alternate(struct string_list_item *item, void *cb_data)\n {\n+\tstruct add_one_alternate_data *data = cb_data;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint *required = cb_data;\n \tchar *ref_git = compute_alternate_path(item->string, &err);\n \n \tif (!ref_git) {\n-\t\tif (*required)\n+\t\tif (data->required)\n \t\t\tdie(\"%s\", err.buf);\n \t\telse\n \t\t\tfprintf(stderr,\n \t\t\t\t_(\"info: Could not add alternate for '%s': %s\\n\"),\n \t\t\t\titem->string, err.buf);\n \t} else {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\tstrbuf_addf(&sb, \"%s/objects\", ref_git);\n-\t\todb_add_to_alternates_file(the_repository->objects, sb.buf);\n-\t\tstrbuf_release(&sb);\n+\t\tstrvec_pushf(data->alternates, \"%s/objects\", ref_git);\n \t}\n \n \tstrbuf_release(&err);\n@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n-static void setup_reference(void)\n+static void collect_alternates(struct strvec *alternates)\n {\n-\tint required = 1;\n-\tfor_each_string_list(&option_required_reference,\n-\t\t\t     add_one_reference, &required);\n-\trequired = 0;\n-\tfor_each_string_list(&option_optional_reference,\n-\t\t\t     add_one_reference, &required);\n+\tif (option_required_reference.nr || option_optional_reference.nr) {\n+\t\tstruct add_one_alternate_data data = {\n+\t\t\t.alternates = alternates,\n+\t\t\t.required = 1,\n+\t\t};\n+\n+\t\tfor_each_string_list(&option_required_reference,\n+\t\t\t\t     add_one_alternate, &data);\n+\t\tdata.required = 0;\n+\t\tfor_each_string_list(&option_optional_reference,\n+\t\t\t\t     add_one_alternate, &data);\n+\t}\n }\n \n static void mkdir_if_missing(const char *pathname, mode_t mode)\n@@ -999,6 +1007,7 @@ int cmd_clone(int argc,\n \t\t\t   N_(\"uri\"), N_(\"a URI for downloading bundles before fetching from origin remote\")),\n \t\tOPT_END()\n \t};\n+\tstruct strvec alternates = STRVEC_INIT;\n \n \tconst char * const builtin_clone_usage[] = {\n \t\tN_(\"git clone [<options>] [--] <repo> [<dir>]\"),\n@@ -1339,8 +1348,10 @@ int cmd_clone(int argc,\n \t\twarning(_(\"--local is ignored\"));\n \n \tcreate_object_database(the_repository);\n-\tif (option_required_reference.nr || option_optional_reference.nr)\n-\t\tsetup_reference();\n+\tcollect_alternates(&alternates);\n+\n+\tfor (size_t i = 0; i < alternates.nr; i++)\n+\t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n \n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\n@@ -1638,6 +1649,7 @@ int cmd_clone(int argc,\n \tstring_list_clear(&option_not, 0);\n \tstring_list_clear(&option_config, 0);\n \tstring_list_clear(&server_options, 0);\n+\tstrvec_clear(&alternates);\n \n \tfree(remote_name);\n \tstrbuf_release(&reflog_msg);\n\n-- \n2.55.0.1007.g17ff1f9808.dirty\n\n"},{"id":"552110","messageId":"20260907-pks-odb-write-alternates-at-creation-time-v3-5-735d0b5b3e00@pks.im","threadId":"66216","inReplyTo":"20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im","subject":"[PATCH v3 5/9] builtin/clone: move setup of alternates for shared local clones","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-07T08:25:42Z","receivedAt":"2026-09-07T08:26:02Z","isPatch":true,"body":"When cloning a local repository with \"--shared\" we add that repository\nto the new repository's alternates. This is done in `clone_local()`,\nwhich is responsible for performing local clones.\n\nMove the logic into `collect_alternates()` to unify our setup of\nalternates. Furthermore, this will allow us to set up alternates right\nat creation time of the object database.\n\nNote that the logic for cloning a local repository with \"--no-shared\" is\nnot yet part of `collect_alternates()`. This will be handled in the next\ncommit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 23 ++++++++++++++---------\n 1 file changed, 14 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 8786a49332..011fc867c8 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n-static void collect_alternates(struct strvec *alternates)\n+static void collect_alternates(struct strvec *alternates,\n+\t\t\t       const char *src_repo, bool is_local)\n {\n \tif (option_required_reference.nr || option_optional_reference.nr) {\n \t\tstruct add_one_alternate_data data = {\n@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)\n \t\tfor_each_string_list(&option_optional_reference,\n \t\t\t\t     add_one_alternate, &data);\n \t}\n+\n+\tif (is_local) {\n+\t\tstruct strbuf commondir = STRBUF_INIT;\n+\n+\t\tget_common_dir(&commondir, src_repo);\n+\t\tif (option_shared)\n+\t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n+\n+\t\tstrbuf_release(&commondir);\n+\t}\n }\n \n static void mkdir_if_missing(const char *pathname, mode_t mode)\n@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,\n \n static void clone_local(const char *src_repo, const char *dest_repo)\n {\n-\tif (option_shared) {\n-\t\tstruct strbuf alt = STRBUF_INIT;\n-\t\tget_common_dir(&alt, src_repo);\n-\t\tstrbuf_addstr(&alt, \"/objects\");\n-\t\todb_add_to_alternates_file(the_repository->objects, alt.buf);\n-\t\tstrbuf_release(&alt);\n-\t} else {\n+\tif (!option_shared) {\n \t\tstruct strbuf src = STRBUF_INIT;\n \t\tstruct strbuf dest = STRBUF_INIT;\n \t\tget_common_dir(&src, src_repo);\n@@ -1348,7 +1353,7 @@ int cmd_clone(int argc,\n \t\twarning(_(\"--local is ignored\"));\n \n \tcreate_object_database(the_repository);\n-\tcollect_alternates(&alternates);\n+\tcollect_alternates(&alternates, path, is_local);\n \n \tfor (size_t i = 0; i < alternates.nr; i++)\n \t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n\n-- \n2.55.0.1007.g17ff1f9808.dirty\n\n"},{"id":"552111","messageId":"20260907-pks-odb-write-alternates-at-creation-time-v3-6-735d0b5b3e00@pks.im","threadId":"66216","inReplyTo":"20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im","subject":"[PATCH v3 6/9] builtin/clone: move setup of alternates for non-shared local clones","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-07T08:25:43Z","receivedAt":"2026-09-07T08:26:05Z","isPatch":true,"body":"Similar as in the preceding commit, move the setup of alternates for\nlocal clones with \"--no-shared\" into `collect_alternates()`. With this\nstep, the complete setup of alternates is now handled by that function.\n\nNote that besides moving stuff around, it also fixes a bug: previously,\nwe did not know to resolve the referenced repository's common directory.\nConsequently, when referencing a worktree we failed to resolve\nalternates. But as `collect_alternates()` already knows to resolve the\ncommondir for \"--local\" we can simply reuse this resolved path for our\npurpose.\n\nAdd two tests, the first one of which exercises this bug to avoid future\nregressions. The second test ensures that we properly handle relative\nalternates for a referenced worktree.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c            | 34 +++++++++++++++++++++++-----------\n t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 48 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 011fc867c8..84c1317867 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)\n \treturn 0;\n }\n \n-static void copy_alternates(struct strbuf *src, const char *src_repo)\n+static void read_alternates(struct strvec *alternates, const char *src_repo)\n {\n \t/*\n \t * Read from the source objects/info/alternates file\n@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \t * to turn entries with paths relative to the original\n \t * absolute, so that they can be used in the new repository.\n \t */\n-\tFILE *in = xfopen(src->buf, \"r\");\n+\tFILE *in;\n+\tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf line = STRBUF_INIT;\n \n+\tstrbuf_addf(&path, \"%s/objects/info/alternates\", src_repo);\n+\n+\tin = fopen(path.buf, \"r\");\n+\tif (!in) {\n+\t\tif (errno == ENOENT)\n+\t\t\tgoto out;\n+\t\tdie_errno(\"could not read alternates file '%s'\", path.buf);\n+\t}\n+\n \twhile (strbuf_getline(&line, in) != EOF) {\n \t\tchar *abs_path;\n \t\tif (!line.len || line.buf[0] == '#')\n \t\t\tcontinue;\n \t\tif (is_absolute_path(line.buf)) {\n-\t\t\todb_add_to_alternates_file(the_repository->objects,\n-\t\t\t\t\t\t   line.buf);\n+\t\t\tstrvec_push(alternates, line.buf);\n \t\t\tcontinue;\n \t\t}\n \t\tabs_path = mkpathdup(\"%s/objects/%s\", src_repo, line.buf);\n \t\tif (!normalize_path_copy(abs_path, abs_path))\n-\t\t\todb_add_to_alternates_file(the_repository->objects,\n-\t\t\t\t\t\t   abs_path);\n+\t\t\tstrvec_push(alternates, abs_path);\n \t\telse\n \t\t\twarning(\"skipping invalid relative alternate: %s/%s\",\n \t\t\t\tsrc_repo, line.buf);\n \t\tfree(abs_path);\n \t}\n+\n+out:\n+\tstrbuf_release(&path);\n \tstrbuf_release(&line);\n-\tfclose(in);\n+\tif (in)\n+\t\tfclose(in);\n }\n \n static void collect_alternates(struct strvec *alternates,\n@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,\n \t\tget_common_dir(&commondir, src_repo);\n \t\tif (option_shared)\n \t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n+\t\telse\n+\t\t\tread_alternates(alternates, commondir.buf);\n \n \t\tstrbuf_release(&commondir);\n \t}\n@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,\n \t\t\tcontinue;\n \t\t}\n \n-\t\t/* Files that cannot be copied bit-for-bit... */\n-\t\tif (!fspathcmp(iter->relative_path, \"info/alternates\")) {\n-\t\t\tcopy_alternates(src, src_repo);\n+\t\t/* Alternates were already handled earlier. */\n+\t\tif (!fspathcmp(iter->relative_path, \"info/alternates\"))\n \t\t\tcontinue;\n-\t\t}\n \n \t\tif (unlink(dest->buf) && errno != ENOENT)\n \t\t\tdie_errno(_(\"failed to unlink '%s'\"), dest->buf);\ndiff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh\nindex 39a0c318df..9e4b98fdb8 100755\n--- a/t/t5604-clone-reference.sh\n+++ b/t/t5604-clone-reference.sh\n@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '\n \tgit clone --no-local --reference graph.git --dissociate orig clone\n '\n \n+test_expect_success 'local clone from linked worktree carries over alternates' '\n+\trm -fr base derived derived-wt dst expect &&\n+\tgit init base &&\n+\ttest_commit -C base one &&\n+\tgit clone --shared base derived &&\n+\tgit -C derived worktree add ../derived-wt &&\n+\tgit clone derived-wt dst &&\n+\techo \"$(pwd)/base/.git/objects\" >expect &&\n+\ttest_cmp expect dst/.git/objects/info/alternates &&\n+\tgit -C dst fsck\n+'\n+\n+test_expect_success 'local clone from linked worktree resolves relative alternates' '\n+\trm -fr base derived derived-wt dst expect &&\n+\tgit init base &&\n+\ttest_commit -C base one &&\n+\tgit clone --shared base derived &&\n+\techo \"../../../base/.git/objects\" >derived/.git/objects/info/alternates &&\n+\tgit -C derived worktree add ../derived-wt &&\n+\tgit clone derived-wt dst &&\n+\techo \"$(pwd)/base/.git/objects\" >expect &&\n+\ttest_cmp expect dst/.git/objects/info/alternates &&\n+\tgit -C dst fsck\n+'\n+\n test_done\n\n-- \n2.55.0.1007.g17ff1f9808.dirty\n\n"},{"id":"552112","messageId":"20260907-pks-odb-write-alternates-at-creation-time-v3-7-735d0b5b3e00@pks.im","threadId":"66216","inReplyTo":"20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im","subject":"[PATCH v3 7/9] odb/source: support writing alternates when creating the database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-07T08:25:44Z","receivedAt":"2026-09-07T08:26:07Z","isPatch":true,"body":"Add the ability to write alternates when creating the object database.\nThis change allows us to remove the `write_alternates()` callback in a\nsubsequent patch.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--\n odb/source.h       | 17 +++++++++---\n setup.c            |  4 ++-\n 3 files changed, 91 insertions(+), 6 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex b7b3a297bb..8fe65d91f8 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -18,6 +18,7 @@\n #include \"run-command.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n+#include \"strmap.h\"\n #include \"strvec.h\"\n #include \"tree.h\"\n #include \"write-or-die.h\"\n@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)\n \todb_source_close(&files->packed->base);\n }\n \n-static int odb_source_files_create_on_disk(struct odb_source *source)\n+static int odb_source_files_create_on_disk(struct odb_source *source,\n+\t\t\t\t\t   const struct odb_create_on_disk_options *opts)\n {\n+\tstruct lock_file alternates_lock = LOCK_INIT;\n \tstruct strbuf path = STRBUF_INIT;\n+\tstruct strset seen = STRSET_INIT;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tint ret;\n \n \tsafe_create_dir(source->odb->repo, source->path, 1);\n \n@@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)\n \tstrbuf_addf(&path, \"%s/info\", source->path);\n \tsafe_create_dir(source->odb->repo, path.buf, 1);\n \n+\tif (opts->alternates && opts->alternates->nr) {\n+\t\tFILE *alternates, *orig;\n+\n+\t\tstrbuf_reset(&path);\n+\t\tstrbuf_addf(&path, \"%s/info/alternates\", source->path);\n+\n+\t\trepo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,\n+\t\t\t\t\t       path.buf, LOCK_DIE_ON_ERROR);\n+\n+\t\talternates = fdopen_lock_file(&alternates_lock, \"w\");\n+\t\tif (!alternates) {\n+\t\t\tret = error_errno(_(\"unable to fdopen alternates lockfile\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/*\n+\t\t * The alternates file may already exist, e.g. when it has been\n+\t\t * seeded from a template directory. Read any preexisting\n+\t\t * entries so that we don't end up writing duplicates.\n+\t\t */\n+\t\torig = fopen(path.buf, \"r\");\n+\t\tif (orig) {\n+\t\t\twhile (strbuf_getline(&line, orig) != EOF) {\n+\t\t\t\tstrset_add(&seen, line.buf);\n+\t\t\t\tfprintf(alternates, \"%s\\n\", line.buf);\n+\t\t\t}\n+\n+\t\t\tif (ferror(orig)) {\n+\t\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n+\t\t\t\tfclose(orig);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\tfclose(orig);\n+\t\t} else if (errno != ENOENT) {\n+\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (size_t i = 0; i < opts->alternates->nr; i++) {\n+\t\t\tconst char *alternate = opts->alternates->v[i];\n+\t\t\tif (!strset_add(&seen, alternate))\n+\t\t\t\tcontinue;\n+\t\t\tfprintf(alternates, \"%s\\n\", alternate);\n+\t\t}\n+\n+\t\tif (ferror(alternates)) {\n+\t\t\tret = error_errno(_(\"unable to write alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (commit_lock_file(&alternates_lock)) {\n+\t\t\tret = error_errno(_(\"unable to commit alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+\t/* Reprepare the object database to activate alternates. */\n+\todb_reprepare(source->odb);\n+\n+\tret = 0;\n+\n+out:\n+\trollback_lock_file(&alternates_lock);\n+\tstrbuf_release(&line);\n \tstrbuf_release(&path);\n-\treturn 0;\n+\tstrset_clear(&seen);\n+\treturn ret;\n }\n \n static void odb_source_files_prepare(struct odb_source *source,\ndiff --git a/odb/source.h b/odb/source.h\nindex ea8675247e..63f1c0c531 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -36,6 +36,15 @@ struct object_id;\n struct odb_stream;\n struct strvec;\n \n+struct odb_create_on_disk_options {\n+\t/*\n+\t * Alternates that shall be written into the newly created object\n+\t * database. Whether or not this option can be handled is specific to\n+\t * the backend.\n+\t */\n+\tconst struct strvec *alternates;\n+};\n+\n /*\n  * The source is the part of the object database that stores the actual\n  * objects. It thus encapsulates the logic to read and write the specific\n@@ -106,7 +115,8 @@ struct odb_source {\n \t * This callback may be NULL in case the source does not need any\n \t * on-disk setup.\n \t */\n-\tint (*create_on_disk)(struct odb_source *source);\n+\tint (*create_on_disk)(struct odb_source *source,\n+\t\t\t      const struct odb_create_on_disk_options *opts);\n \n \t/*\n \t * This callback is expected to prepare the source so that it becomes\n@@ -356,11 +366,12 @@ static inline void odb_source_close(struct odb_source *source)\n  * Create on-disk data structures that are required for this source to operate\n  * correctly. Returns 0 on success, a negative error code otherwise.\n  */\n-static inline int odb_source_create_on_disk(struct odb_source *source)\n+static inline int odb_source_create_on_disk(struct odb_source *source,\n+\t\t\t\t\t    const struct odb_create_on_disk_options *opts)\n {\n \tif (!source->create_on_disk)\n \t\treturn 0;\n-\treturn source->create_on_disk(source);\n+\treturn source->create_on_disk(source, opts);\n }\n \n /*\ndiff --git a/setup.c b/setup.c\nindex 8c7b97f92e..37a8e6f124 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,\n \n void create_object_database(struct repository *repo)\n {\n+\tstruct odb_create_on_disk_options opts = { 0 };\n+\n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\n \t * so that the repository can be discovered regardless of the backend\n@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)\n \n \trepo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);\n \n-\tif (odb_source_create_on_disk(repo->objects->sources) < 0)\n+\tif (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)\n \t\tdie(_(\"failed creating object database\"));\n }\n \n\n-- \n2.55.0.1007.g17ff1f9808.dirty\n\n"},{"id":"552113","messageId":"20260907-pks-odb-write-alternates-at-creation-time-v3-8-735d0b5b3e00@pks.im","threadId":"66216","inReplyTo":"20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im","subject":"[PATCH v3 8/9] builtin/clone: write alternates via `odb_create_on_disk()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-07T08:25:45Z","receivedAt":"2026-09-07T08:26:10Z","isPatch":true,"body":"When creating a repository with alternates we first initialize the\nobject database and then write alternates to it in a separate step. This\nis unfortunate due to a couple of reasons:\n\n  - It requires us to have a `write_alternates()` callback, which is\n    unfortunate as we never even write alternates to an object database\n    after it has been created.\n\n  - We're about to make alternates an implementation detail of the\n    object database's backend in a future patch series, so having this\n    callback is suboptimal there.\n\n  - The backend has more flexibility with how exactly alternates are\n    configured when it itself is in full control over their setup at the\n    time where it creates the object database itself.\n\nWe have thus introduced the ability to write alternates right at\ncreation time in the preceding commits, and we have unified setup of\nalternates into a single location. All that's left to do for us now is\nto wire up alternates as an option for the database creation.\n\nDo so.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c   | 5 +----\n builtin/init-db.c | 2 +-\n setup.c           | 7 +++++--\n setup.h           | 2 +-\n 4 files changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 84c1317867..9e84646845 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1364,11 +1364,8 @@ int cmd_clone(int argc,\n \tif (option_local > 0 && !is_local)\n \t\twarning(_(\"--local is ignored\"));\n \n-\tcreate_object_database(the_repository);\n \tcollect_alternates(&alternates, path, is_local);\n-\n-\tfor (size_t i = 0; i < alternates.nr; i++)\n-\t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n+\tcreate_object_database(the_repository, &alternates);\n \n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex f2c7e3be6d..5c22eae2f3 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -251,7 +251,7 @@ int cmd_init_db(int argc,\n \t\t\t  template_dir, hash_algo, ref_storage_format,\n \t\t\t  init_shared_repository, &reinit);\n \tcreate_reference_database(the_repository, initial_branch, quiet);\n-\tcreate_object_database(the_repository);\n+\tcreate_object_database(the_repository, NULL);\n \n \tif (!quiet) {\n \t\tint len = strlen(git_dir);\ndiff --git a/setup.c b/setup.c\nindex 37a8e6f124..17d0d25973 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,\n \treturn reinit;\n }\n \n-void create_object_database(struct repository *repo)\n+void create_object_database(struct repository *repo,\n+\t\t\t    const struct strvec *alternates)\n {\n-\tstruct odb_create_on_disk_options opts = { 0 };\n+\tstruct odb_create_on_disk_options opts = {\n+\t\t.alternates = alternates,\n+\t};\n \n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\ndiff --git a/setup.h b/setup.h\nindex c4aa464caa..e9941c6875 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -270,7 +270,7 @@ void initialize_repository_version(struct repository *repo,\n \t\t\t\t   enum ref_storage_format ref_storage_format,\n \t\t\t\t   int reinit);\n void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);\n-void create_object_database(struct repository *repo);\n+void create_object_database(struct repository *repo, const struct strvec *alternates);\n \n /*\n  * NOTE NOTE NOTE!!\n\n-- \n2.55.0.1007.g17ff1f9808.dirty\n\n"},{"id":"552114","messageId":"20260907-pks-odb-write-alternates-at-creation-time-v3-9-735d0b5b3e00@pks.im","threadId":"66216","inReplyTo":"20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im","subject":"[PATCH v3 9/9] odb/source: remove the ability to write alternates","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-07T08:25:46Z","receivedAt":"2026-09-07T08:26:12Z","isPatch":true,"body":"There are no users of `odb_source_write_alternates()` in our tree\nanymore. Remove that function and its supporting infrastructure.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c                 |  9 ---------\n odb.h                 |  7 -------\n odb/source-files.c    | 54 ---------------------------------------------------\n odb/source-inmemory.c |  7 -------\n odb/source-loose.c    |  7 -------\n odb/source-packed.c   |  7 -------\n odb/source.h          | 26 -------------------------\n 7 files changed, 117 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 67d98d64fc..b531cf8fb3 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *\n \treturn alternate;\n }\n \n-void odb_add_to_alternates_file(struct object_database *odb,\n-\t\t\t\tconst char *dir)\n-{\n-\tint ret = odb_source_write_alternate(odb->sources, dir);\n-\tif (ret < 0)\n-\t\tdie(NULL);\n-\todb_add_alternate_recursively(odb, dir, 0);\n-}\n-\n struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,\n \t\t\t\t\t\tconst char *dir)\n {\ndiff --git a/odb.h b/odb.h\nindex b9e0db56ec..2d002461f8 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,\n  */\n int odb_has_alternates(struct object_database *odb);\n \n-/*\n- * Add the directory to the on-disk alternates file; the new entry will also\n- * take effect in the current process.\n- */\n-void odb_add_to_alternates_file(struct object_database *odb,\n-\t\t\t\tconst char *dir);\n-\n /*\n  * Add the directory to the in-memory list of alternate sources (along with any\n  * recursive alternates it points to), but do not modify the on-disk alternates\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 8fe65d91f8..b3f340dff8 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -306,59 +306,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,\n \treturn 0;\n }\n \n-static int odb_source_files_write_alternate(struct odb_source *source,\n-\t\t\t\t\t    const char *alternate)\n-{\n-\tstruct lock_file lock = LOCK_INIT;\n-\tchar *path = xstrfmt(\"%s/%s\", source->path, \"info/alternates\");\n-\tFILE *in, *out;\n-\tint found = 0;\n-\tint ret;\n-\n-\trepo_hold_lock_file_for_update(source->odb->repo, &lock, path,\n-\t\t\t\t       LOCK_DIE_ON_ERROR);\n-\tout = fdopen_lock_file(&lock, \"w\");\n-\tif (!out) {\n-\t\tret = error_errno(_(\"unable to fdopen alternates lockfile\"));\n-\t\tgoto out;\n-\t}\n-\n-\tin = fopen(path, \"r\");\n-\tif (in) {\n-\t\tstruct strbuf line = STRBUF_INIT;\n-\n-\t\twhile (strbuf_getline(&line, in) != EOF) {\n-\t\t\tif (!strcmp(alternate, line.buf)) {\n-\t\t\t\tfound = 1;\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t\tfprintf_or_die(out, \"%s\\n\", line.buf);\n-\t\t}\n-\n-\t\tstrbuf_release(&line);\n-\t\tfclose(in);\n-\t} else if (errno != ENOENT) {\n-\t\tret = error_errno(_(\"unable to read alternates file\"));\n-\t\tgoto out;\n-\t}\n-\n-\tif (found) {\n-\t\trollback_lock_file(&lock);\n-\t} else {\n-\t\tfprintf_or_die(out, \"%s\\n\", alternate);\n-\t\tif (commit_lock_file(&lock)) {\n-\t\t\tret = error_errno(_(\"unable to move new alternates file into place\"));\n-\t\t\tgoto out;\n-\t\t}\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\tfree(path);\n-\treturn ret;\n-}\n-\n static int too_many_loose_objects(struct odb_source_files *files, int limit)\n {\n \tunsigned long loose_count;\n@@ -842,7 +789,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tfiles->base.write_object_stream = odb_source_files_write_object_stream;\n \tfiles->base.begin_transaction = odb_source_files_begin_transaction;\n \tfiles->base.read_alternates = odb_source_files_read_alternates;\n-\tfiles->base.write_alternate = odb_source_files_write_alternate;\n \tfiles->base.optimize = odb_source_files_optimize;\n \tfiles->base.optimize_required = odb_source_files_optimize_required;\n \ndiff --git a/odb/source-inmemory.c b/odb/source-inmemory.c\nindex 795672adf2..b00248dfb2 100644\n--- a/odb/source-inmemory.c\n+++ b/odb/source-inmemory.c\n@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t       const char *alternate UNUSED)\n-{\n-\treturn error(\"in-memory source does not support alternates\");\n-}\n-\n static void odb_source_inmemory_close(struct odb_source *source UNUSED)\n {\n }\n@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)\n \tsource->base.freshen_object = odb_source_inmemory_freshen_object;\n \tsource->base.begin_transaction = odb_source_inmemory_begin_transaction;\n \tsource->base.read_alternates = odb_source_inmemory_read_alternates;\n-\tsource->base.write_alternate = odb_source_inmemory_write_alternate;\n \n \treturn source;\n }\ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex bb3455dfbd..0f9b30bac1 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t    const char *alternate UNUSED)\n-{\n-\treturn error(\"loose source does not support alternates\");\n-}\n-\n static void odb_source_loose_clear_cache(struct odb_source_loose *loose)\n {\n \toidtree_clear(loose->cache);\n@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n \tloose->base.write_object_stream = odb_source_loose_write_object_stream;\n \tloose->base.begin_transaction = odb_source_loose_begin_transaction;\n \tloose->base.read_alternates = odb_source_loose_read_alternates;\n-\tloose->base.write_alternate = odb_source_loose_write_alternate;\n \n \tif (!is_absolute_path(loose->base.path))\n \t\tchdir_notify_register(NULL, odb_source_loose_reparent, loose);\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 630d955585..c2d253759c 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t     const char *alternate UNUSED)\n-{\n-\treturn error(\"packed backend cannot write alternates\");\n-}\n-\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n \tpacked->base.write_object_stream = odb_source_packed_write_object_stream;\n \tpacked->base.begin_transaction = odb_source_packed_begin_transaction;\n \tpacked->base.read_alternates = odb_source_packed_read_alternates;\n-\tpacked->base.write_alternate = odb_source_packed_write_alternate;\n \n \tif (!is_absolute_path(path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/odb/source.h b/odb/source.h\nindex 63f1c0c531..693a9fc604 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -286,19 +286,6 @@ struct odb_source {\n \tint (*read_alternates)(struct odb_source *source,\n \t\t\t       struct strvec *out);\n \n-\t/*\n-\t * This callback is expected to persist the singular alternate passed\n-\t * to it into its list of alternates. Any pre-existing alternates are\n-\t * expected to remain active. Subsequent calls to `read_alternates` are\n-\t * thus expected to yield the pre-existing list of alternates plus the\n-\t * newly added alternate appended to its end.\n-\t *\n-\t * The callback is expected to return 0 on success, a negative error\n-\t * code otherwise.\n-\t */\n-\tint (*write_alternate)(struct odb_source *source,\n-\t\t\t       const char *alternate);\n-\n \t/*\n \t * This callback is expected to optimize the object database source.\n \t * Returns 0 on success, a negative error code otherwise.\n@@ -518,19 +505,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,\n \treturn source->read_alternates(source, out);\n }\n \n-/*\n- * Write and persist a new alternate object database source for the given\n- * source. Any preexisting alternates are expected to stay valid, and the new\n- * alternate shall be appended to the end of the list.\n- *\n- * Returns 0 on success, a negative error code otherwise.\n- */\n-static inline int odb_source_write_alternate(struct odb_source *source,\n-\t\t\t\t\t      const char *alternate)\n-{\n-\treturn source->write_alternate(source, alternate);\n-}\n-\n /*\n  * Create a new transaction that can be used to write objects into a temporary\n  * staging area. The objects will only be persisted when the transaction is\n\n-- \n2.55.0.1007.g17ff1f9808.dirty\n\n"},{"id":"552265","messageId":"aqCE801dAwP7pAU5@denethor","threadId":"66216","inReplyTo":"20260907-pks-odb-write-alternates-at-creation-time-v3-1-735d0b5b3e00@pks.im","subject":"Re: [PATCH v3 1/9] setup: split up concerns of `init_db()`","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-09-08T22:12:21Z","receivedAt":"2026-09-08T22:12:27Z","isPatch":true,"body":"On 26/09/07 10:25AM, Patrick Steinhardt wrote:\n> The function `init_db()` is responsible for creating the on-disk\n> directory structure required for a Git repository. It is used by both\n> git-init(1) and git-clone(1), and because their expected behaviour is\n> different we support a couple of flags:\n> \n>   - The `QUIET` flag controls whether the command is quiet or not. For\n>     git-init(1) this is user-controllable, whereas for git-clone(1)\n>     we're always quiet.\n> \n>   - The `EXIST_OK` flag controls whether a preexisting repository is\n>     okay or not. For git-init(1) it is, for git-clone(1) it's not.\n> \n>   - The `SKIP_REFDB` flag controls whether the reference database should\n>     already be created or not. For git-init(1) we do, but for\n>     git-clone(1) we don't because it does not yet know about the default\n>     branch and about the remote object hash.\n> \n> Furthermore, we're about to add another divergence in behaviour, where\n> we have to also skip creation of the object database in git-clone(1).\n> This is becoming quite cumbersome though.\n> \n> Instead of introducing another flag, start to split up concerns of the\n> function so that we never create the reference or object database. This\n> becomes the responsibility of the caller, which is thus free to defer\n> their creation to a later point in time. This lets us get rid of most of\n> the divergent behaviour:\n> \n>   - We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags\n>     anymore.\n> \n>   - We don't need the `QUIET` flag anymore, as nothing prints output\n>     except for the final status message that tells the user that the\n>     repository has been (re)initialized. But as this message is specific\n>     to git-init(1), we can easily move it there.\n> \n> The only piece of information we still have to convey is whether or not\n> reinitialization of a preexisting repository is okay. This is handled by\n> a new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay\n> to reinitialize the repository. Furthermore, the pointer will be written\n> to to indicate whether the repository was reinitialized or not, which we\n> need in git-init(1) to print the correct initialization message.\n\nI like the proposed direction here much more than the previous version.\n:)\n\n> With these refactorings, `init_db()` is named quite misleadingly though,\n> as we don't create any of the reference or object databases anymore.\n> Rename it to `create_repository()`.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/clone.c   |  9 +++++----\n>  builtin/init-db.c | 32 ++++++++++++++++++++++++--------\n>  setup.c           | 54 +++++++++++++++++-------------------------------------\n>  setup.h           | 22 ++++++++++------------\n>  4 files changed, 56 insertions(+), 61 deletions(-)\n> \n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index 5b25cca510..904d2d859f 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -1185,10 +1185,10 @@ int cmd_clone(int argc,\n>  \t * repository, and reference backends may persist that information into\n>  \t * their on-disk data structures.\n>  \t */\n> -\tinit_db(the_repository, git_dir, real_git_dir, work_tree, option_template,\n> -\t\tGIT_HASH_UNKNOWN, ref_storage_format, NULL,\n> -\t\tdo_not_override_repo_unix_permissions,\n> -\t\tINIT_DB_QUIET | INIT_DB_SKIP_REFDB);\n> +\tcreate_repository(the_repository, git_dir, real_git_dir, work_tree,\n> +\t\t\t  option_template, GIT_HASH_UNKNOWN, ref_storage_format,\n> +\t\t\t  do_not_override_repo_unix_permissions, NULL);\n> +\tcreate_object_database(the_repository);\n\nThe implicit creation of the ref DB was already skipped so we only need\nto explicitly add `create_object_database()`. Makes sense.\n\n>  \tif (real_git_dir) {\n>  \t\tfree((char *)git_dir);\n> @@ -1445,6 +1445,7 @@ int cmd_clone(int argc,\n>  \tinitialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);\n>  \trepo_set_hash_algo(the_repository, hash_algo);\n>  \tcreate_reference_database(the_repository, NULL, 1);\n> +\tstartup_info->have_repository = 1;\n\nOk IIUC, we now have to explicitly set \"have_repository\" because we\ndon't want to set it if the ODB has not been created.\n\n>  \t/*\n>  \t * Before fetching from the remote, download and install bundle\n> diff --git a/builtin/init-db.c b/builtin/init-db.c\n> index e96b1283b7..f2c7e3be6d 100644\n> --- a/builtin/init-db.c\n> +++ b/builtin/init-db.c\n> @@ -80,7 +80,7 @@ int cmd_init_db(int argc,\n>  \tchar *work_tree = NULL;\n>  \tconst char *template_dir = NULL;\n>  \tchar *template_dir_to_free = NULL;\n> -\tunsigned int flags = 0;\n> +\tint quiet = 0;\n>  \tint bare = startup_info->force_bare_repository ? 1 : -1;\n>  \tconst char *object_format = NULL;\n>  \tconst char *ref_format = NULL;\n> @@ -102,7 +102,7 @@ int cmd_init_db(int argc,\n>  \t\t\t.flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG,\n>  \t\t\t.callback = shared_callback\n>  \t\t},\n> -\t\tOPT_BIT('q', \"quiet\", &flags, N_(\"be quiet\"), INIT_DB_QUIET),\n> +\t\tOPT_BOOL('q', \"quiet\", &quiet, N_(\"be quiet\")),\n>  \t\tOPT_STRING(0, \"separate-git-dir\", &real_git_dir, N_(\"gitdir\"),\n>  \t\t\t   N_(\"separate git dir from working tree\")),\n>  \t\tOPT_STRING('b', \"initial-branch\", &initial_branch, N_(\"name\"),\n> @@ -113,7 +113,7 @@ int cmd_init_db(int argc,\n>  \t\t\t   N_(\"specify the reference format to use\")),\n>  \t\tOPT_END()\n>  \t};\n> -\tint ret;\n> +\tint reinit;\n>  \n>  \targc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);\n>  \n> @@ -247,14 +247,30 @@ int cmd_init_db(int argc,\n>  \t\tdie(_(\"--separate-git-dir incompatible with bare repository\"));\n>  \t}\n>  \n> -\tflags |= INIT_DB_EXIST_OK;\n> -\tret = init_db(the_repository, git_dir, real_git_dir, work_tree,\n> -\t\t      template_dir, hash_algo, ref_storage_format, initial_branch,\n> -\t\t      init_shared_repository, flags);\n> +\tcreate_repository(the_repository, git_dir, real_git_dir, work_tree,\n> +\t\t\t  template_dir, hash_algo, ref_storage_format,\n> +\t\t\t  init_shared_repository, &reinit);\n> +\tcreate_reference_database(the_repository, initial_branch, quiet);\n> +\tcreate_object_database(the_repository);\n> +\n> +\tif (!quiet) {\n> +\t\tint len = strlen(git_dir);\n> +\n> +\t\tif (reinit)\n> +\t\t\tprintf(repo_settings_get_shared_repository(the_repository)\n> +\t\t\t       ? _(\"Reinitialized existing shared Git repository in %s%s\\n\")\n> +\t\t\t       : _(\"Reinitialized existing Git repository in %s%s\\n\"),\n> +\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n> +\t\telse\n> +\t\t\tprintf(repo_settings_get_shared_repository(the_repository)\n> +\t\t\t       ? _(\"Initialized empty shared Git repository in %s%s\\n\")\n> +\t\t\t       : _(\"Initialized empty Git repository in %s%s\\n\"),\n> +\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n> +\t}\n\nPreviously all of this logic was specific to git-init(1), so now its\nmoved out of the generic function accordingly to furthur simplify the\ninterface. Nice.\n\n[snip]\n> diff --git a/setup.h b/setup.h\n> index 763fd384e8..c4aa464caa 100644\n> --- a/setup.h\n> +++ b/setup.h\n> @@ -256,23 +256,21 @@ int apply_repository_format(struct repository *repo,\n>  \n>  const char *get_template_dir(const char *option_template);\n>  \n> -#define INIT_DB_QUIET      (1 << 0)\n> -#define INIT_DB_EXIST_OK   (1 << 1)\n> -#define INIT_DB_SKIP_REFDB (1 << 2)\n> -\n> -int init_db(struct repository *repo,\n> -\t    const char *git_dir,\n> -\t    const char *real_git_dir,\n> -\t    const char *worktree,\n> -\t    const char *template_dir, int hash_algo,\n> -\t    enum ref_storage_format ref_storage_format,\n> -\t    const char *initial_branch, int init_shared_repository,\n> -\t    unsigned int flags);\n> +void create_repository(struct repository *repo,\n> +\t\t       const char *git_dir,\n> +\t\t       const char *real_git_dir,\n> +\t\t       const char *worktree,\n> +\t\t       const char *template_dir,\n> +\t\t       int hash_algo,\n> +\t\t       enum ref_storage_format ref_storage_format,\n> +\t\t       int init_shared_repository,\n> +\t\t       int *reinit_ok);\n\nWhile we are here, it might be nice to document these functions a little\nbit. The NULL/non-NULL behavior of `reinit_ok` may not be entirely\nobvious to future readers.\n\n>  void initialize_repository_version(struct repository *repo,\n>  \t\t\t\t   int hash_algo,\n>  \t\t\t\t   enum ref_storage_format ref_storage_format,\n>  \t\t\t\t   int reinit);\n>  void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);\n> +void create_object_database(struct repository *repo);\n\nIt might also be nice to document these functions to explain exactly\nwhat we are \"creating\".\n\n-Justin\n"},{"id":"552266","messageId":"aqCI5pfM3AGtfQsJ@denethor","threadId":"66216","inReplyTo":"20260907-pks-odb-write-alternates-at-creation-time-v3-2-735d0b5b3e00@pks.im","subject":"Re: [PATCH v3 2/9] builtin/clone: defer setup of the object database","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-09-08T22:20:06Z","receivedAt":"2026-09-08T22:20:08Z","isPatch":true,"body":"On 26/09/07 10:25AM, Patrick Steinhardt wrote:\n> When cloning a repository we defer initialization of the reference\n> database. This is because we don't yet know all details required for us\n> to initialize the refdb in the first place. Most importantly, what we\n> are missing is information about the object hash.\n> \n> We don't do the same thing for the object database yet, but here we\n> essentially have the same problem. While the \"files\" database does not\n> need any information about the object format at creation time, alternate\n> backends are likely to require that information so that they can\n> properly set up their data structures.\n> \n> Besides this forward-looking future proofing though, we also have a\n> second use case for deferring initialization of the object database,\n> namely alternates. When initializing the object database we do not yet\n> know whether we'll need alternates or not because this depends on the\n> repository we're about to clone from. If it is a local repository and\n> the user has passed \"--refernce{,-if-able}\", then we will end up writing\n> alternates into the object database.\n> \n> The ugly part though is that we cannot determine where the repository is\n> getting cloned from before it has been initialized. While we of course\n> already have access to the user-provided URI, that URI can be very well\n> rewritten via \"url.<base>.insteadOf\". We can of course read the global-\n> and system-level configuration to resolve it. But we explicitly resolve\n> the URI a second time after we have initialized the repository because\n> it can happen that we copy a \".git/config\" over from our templates, and\n> that file may cause us to rewrite the path.\n> \n> In a subsequent commit though we'll start to write alternates as part of\n> the repository initialization, so we'll need to have the URI properly\n> resolved before we can initialize the object database. This is ugly, but\n> as mentioned above it makes sense for us to defer its initialization\n> anyway so that we also know about the object hash already.\n> \n> Defer creation of the object database until after we have resolved the\n> URI.\n> \n> Note that this also requires us to defer the call to `setup_reference()`\n> until after we have created the object database. While you might think\n> that this function has something to do with references (\"refs/*\"), it is\n> in fact responsible for setting up alternates. Consequently, we can only\n> call it after we have created the object database already.\n\nAh ok, so now that we are deferring ODB creation a bit further, we need\nto also move ref DB creation to afterwards too. Makes sense.\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/clone.c | 8 ++++----\n>  1 file changed, 4 insertions(+), 4 deletions(-)\n> \n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index 904d2d859f..bdcbd7aa1b 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -1188,7 +1188,6 @@ int cmd_clone(int argc,\n>  \tcreate_repository(the_repository, git_dir, real_git_dir, work_tree,\n>  \t\t\t  option_template, GIT_HASH_UNKNOWN, ref_storage_format,\n>  \t\t\t  do_not_override_repo_unix_permissions, NULL);\n> -\tcreate_object_database(the_repository);\n>  \n>  \tif (real_git_dir) {\n>  \t\tfree((char *)git_dir);\n> @@ -1311,9 +1310,6 @@ int cmd_clone(int argc,\n>  \t\tstrbuf_reset(&key);\n>  \t}\n>  \n> -\tif (option_required_reference.nr || option_optional_reference.nr)\n> -\t\tsetup_reference();\n> -\n>  \tremote = remote_get_early(remote_name);\n>  \n>  \tif (!option_rev)\n> @@ -1342,6 +1338,10 @@ int cmd_clone(int argc,\n>  \tif (option_local > 0 && !is_local)\n>  \t\twarning(_(\"--local is ignored\"));\n>  \n> +\tcreate_object_database(the_repository);\n> +\tif (option_required_reference.nr || option_optional_reference.nr)\n> +\t\tsetup_reference();\n\nOk so the move here is really just in preparation for the alternates\nsetup being handled as part of ODB creation in a subsequent patch and\npurely just structural. Looks good.\n\n-Justin\n"},{"id":"552271","messageId":"aqCOe7cf_uSIz_Eu@denethor","threadId":"66216","inReplyTo":"20260907-pks-odb-write-alternates-at-creation-time-v3-5-735d0b5b3e00@pks.im","subject":"Re: [PATCH v3 5/9] builtin/clone: move setup of alternates for shared local clones","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-09-08T22:43:00Z","receivedAt":"2026-09-08T22:43:08Z","isPatch":true,"body":"On 26/09/07 10:25AM, Patrick Steinhardt wrote:\n> When cloning a local repository with \"--shared\" we add that repository\n> to the new repository's alternates. This is done in `clone_local()`,\n> which is responsible for performing local clones.\n> \n> Move the logic into `collect_alternates()` to unify our setup of\n> alternates. Furthermore, this will allow us to set up alternates right\n> at creation time of the object database.\n> \n> Note that the logic for cloning a local repository with \"--no-shared\" is\n> not yet part of `collect_alternates()`. This will be handled in the next\n> commit.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/clone.c | 23 ++++++++++++++---------\n>  1 file changed, 14 insertions(+), 9 deletions(-)\n> \n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index 8786a49332..011fc867c8 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n>  \tfclose(in);\n>  }\n>  \n> -static void collect_alternates(struct strvec *alternates)\n> +static void collect_alternates(struct strvec *alternates,\n> +\t\t\t       const char *src_repo, bool is_local)\n>  {\n>  \tif (option_required_reference.nr || option_optional_reference.nr) {\n>  \t\tstruct add_one_alternate_data data = {\n> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)\n>  \t\tfor_each_string_list(&option_optional_reference,\n>  \t\t\t\t     add_one_alternate, &data);\n>  \t}\n> +\n> +\tif (is_local) {\n\nCould this condtional instead be:\n\n  if (is_local && option_shared)\n\n> +\t\tstruct strbuf commondir = STRBUF_INIT;\n> +\n> +\t\tget_common_dir(&commondir, src_repo);\n> +\t\tif (option_shared)\n> +\t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n> +\n> +\t\tstrbuf_release(&commondir);\n> +\t}\n\n-Justin\n"},{"id":"552272","messageId":"aqCQYbHm_0TVagTi@denethor","threadId":"66216","inReplyTo":"aqCOe7cf_uSIz_Eu@denethor","subject":"Re: [PATCH v3 5/9] builtin/clone: move setup of alternates for shared local clones","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-09-08T22:48:12Z","receivedAt":"2026-09-08T22:48:19Z","isPatch":true,"body":"On 26/09/08 05:43PM, Justin Tobler wrote:\n> On 26/09/07 10:25AM, Patrick Steinhardt wrote:\n> > +\tif (is_local) {\n> \n> Could this condtional instead be:\n> \n>   if (is_local && option_shared)\n\nAhh nevermind, in the next patch we handle `!option_shared`.\n\n-Justin\n"},{"id":"552280","messageId":"aqDzGaYN-RmXgPUh@pks.im","threadId":"66216","inReplyTo":"aqCE801dAwP7pAU5@denethor","subject":"Re: [PATCH v3 1/9] setup: split up concerns of `init_db()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-09T05:48:09Z","receivedAt":"2026-09-09T05:48:17Z","isPatch":true,"body":"On Tue, Sep 08, 2026 at 05:12:21PM -0500, Justin Tobler wrote:\n> On 26/09/07 10:25AM, Patrick Steinhardt wrote:\n> > diff --git a/setup.h b/setup.h\n> > index 763fd384e8..c4aa464caa 100644\n> > --- a/setup.h\n> > +++ b/setup.h\n> > @@ -256,23 +256,21 @@ int apply_repository_format(struct repository *repo,\n> >  \n> >  const char *get_template_dir(const char *option_template);\n> >  \n> > -#define INIT_DB_QUIET      (1 << 0)\n> > -#define INIT_DB_EXIST_OK   (1 << 1)\n> > -#define INIT_DB_SKIP_REFDB (1 << 2)\n> > -\n> > -int init_db(struct repository *repo,\n> > -\t    const char *git_dir,\n> > -\t    const char *real_git_dir,\n> > -\t    const char *worktree,\n> > -\t    const char *template_dir, int hash_algo,\n> > -\t    enum ref_storage_format ref_storage_format,\n> > -\t    const char *initial_branch, int init_shared_repository,\n> > -\t    unsigned int flags);\n> > +void create_repository(struct repository *repo,\n> > +\t\t       const char *git_dir,\n> > +\t\t       const char *real_git_dir,\n> > +\t\t       const char *worktree,\n> > +\t\t       const char *template_dir,\n> > +\t\t       int hash_algo,\n> > +\t\t       enum ref_storage_format ref_storage_format,\n> > +\t\t       int init_shared_repository,\n> > +\t\t       int *reinit_ok);\n> \n> While we are here, it might be nice to document these functions a little\n> bit. The NULL/non-NULL behavior of `reinit_ok` may not be entirely\n> obvious to future readers.\n> \n> >  void initialize_repository_version(struct repository *repo,\n> >  \t\t\t\t   int hash_algo,\n> >  \t\t\t\t   enum ref_storage_format ref_storage_format,\n> >  \t\t\t\t   int reinit);\n> >  void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);\n> > +void create_object_database(struct repository *repo);\n> \n> It might also be nice to document these functions to explain exactly\n> what we are \"creating\".\n\nYeah, fair. Will add some comments here.\n\nPatrick\n"},{"id":"552281","messageId":"aqDzKcce-65R_aRE@pks.im","threadId":"66216","inReplyTo":"aqCI5pfM3AGtfQsJ@denethor","subject":"Re: [PATCH v3 2/9] builtin/clone: defer setup of the object database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-09T05:48:25Z","receivedAt":"2026-09-09T05:48:30Z","isPatch":true,"body":"On Tue, Sep 08, 2026 at 05:20:06PM -0500, Justin Tobler wrote:\n> On 26/09/07 10:25AM, Patrick Steinhardt wrote:\n> > When cloning a repository we defer initialization of the reference\n> > database. This is because we don't yet know all details required for us\n> > to initialize the refdb in the first place. Most importantly, what we\n> > are missing is information about the object hash.\n> > \n> > We don't do the same thing for the object database yet, but here we\n> > essentially have the same problem. While the \"files\" database does not\n> > need any information about the object format at creation time, alternate\n> > backends are likely to require that information so that they can\n> > properly set up their data structures.\n> > \n> > Besides this forward-looking future proofing though, we also have a\n> > second use case for deferring initialization of the object database,\n> > namely alternates. When initializing the object database we do not yet\n> > know whether we'll need alternates or not because this depends on the\n> > repository we're about to clone from. If it is a local repository and\n> > the user has passed \"--refernce{,-if-able}\", then we will end up writing\n> > alternates into the object database.\n> > \n> > The ugly part though is that we cannot determine where the repository is\n> > getting cloned from before it has been initialized. While we of course\n> > already have access to the user-provided URI, that URI can be very well\n> > rewritten via \"url.<base>.insteadOf\". We can of course read the global-\n> > and system-level configuration to resolve it. But we explicitly resolve\n> > the URI a second time after we have initialized the repository because\n> > it can happen that we copy a \".git/config\" over from our templates, and\n> > that file may cause us to rewrite the path.\n> > \n> > In a subsequent commit though we'll start to write alternates as part of\n> > the repository initialization, so we'll need to have the URI properly\n> > resolved before we can initialize the object database. This is ugly, but\n> > as mentioned above it makes sense for us to defer its initialization\n> > anyway so that we also know about the object hash already.\n> > \n> > Defer creation of the object database until after we have resolved the\n> > URI.\n> > \n> > Note that this also requires us to defer the call to `setup_reference()`\n> > until after we have created the object database. While you might think\n> > that this function has something to do with references (\"refs/*\"), it is\n> > in fact responsible for setting up alternates. Consequently, we can only\n> > call it after we have created the object database already.\n> \n> Ah ok, so now that we are deferring ODB creation a bit further, we need\n> to also move ref DB creation to afterwards too. Makes sense.\n\nThis is not about the reference database, `setup_reference()` sets up\nout alternates. But I think you just miswrote here, as you do mention\nalternates further down in your reply.\n\nPatrick\n"},{"id":"552282","messageId":"20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im","subject":"[PATCH v4 0/9] odb: write alternates at creation time","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-09T05:48:42Z","receivedAt":"2026-09-09T05:48:56Z","isPatch":true,"body":"Hi,\n\nwriting alternates into the object database currently happens via\n`odb_source_write_alternate()`. But while that creates the ability to\ncreate alternates at arbitrary points of a source's lifetime, we don't\nuse that functionality in the first place. Instead, we only ever write\nalternates when creating a new repository.\n\nThis design is suboptimal due to a couple of reasons:\n\n  - It requires us to have a `write_alternates()` callback, which is\n    overblown as we never even write alternates to an object database\n    after it has been created.\n\n  - We're about to make alternates an implementation detail of the\n    object database's backend in a future patch series, so alternate\n    implementations may not even support them.\n\n  - The backend has more flexibility with how exactly alternates are\n    configured when it itself is in full control over their setup at the\n    time where it creates the object database itself.\n\nThis patch series thus refactors how we handle alternates so that we\ndon't write them ad-hoc anymore. Instead, the series introduces a new\noption for `odb_source_create_on_disk()` that makes it handle those\nalternates at creation time.\n\nThis is part of the bigger goal of moving handling of alternates into\nthe \"files\" backend.\n\nThis series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)\nwith ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop\n`alternates_db` field, 2026-08-17) merged into it.\n\nChanges in v4:\n  - Add documentation for the different functions that play a role in\n    creating repositories.\n  - Link to v3: https://patch.msgid.link/20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im\n\nChanges in v3:\n  - Refactor `init_db()` to not create the reference and object database\n    at all anymore. Instead, it's now called `create_repository()` and\n    it is responsible for creating the initial repository skeleton,\n    only. This allows us to get rid of the flags and overall makes the\n    logic more straight-forward by moving the command-specific logic\n    into the respective commands.\n  - A couple of typo fixes.\n  - Link to v2: https://patch.msgid.link/20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im\n\nChanges in v2:\n  - Use a lockfile to write \"info/alternates\" during creation time.\n  - Remove useless \"strvec.h\" include by reordering declarations a bit.\n  - Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (9):\n      setup: split up concerns of `init_db()`\n      builtin/clone: defer setup of the object database\n      builtin/clone: move around `setup_reference()`\n      builtin/clone: refactor handling of \"--reference{,-if-able}\"\n      builtin/clone: move setup of alternates for shared local clones\n      builtin/clone: move setup of alternates for non-shared local clones\n      odb/source: support writing alternates when creating the database\n      builtin/clone: write alternates via `odb_create_on_disk()`\n      odb/source: remove the ability to write alternates\n\n builtin/clone.c            | 111 +++++++++++++++++++++++---------------\n builtin/init-db.c          |  32 ++++++++---\n odb.c                      |   9 ----\n odb.h                      |   7 ---\n odb/source-files.c         | 130 ++++++++++++++++++++++++++-------------------\n odb/source-inmemory.c      |   7 ---\n odb/source-loose.c         |   7 ---\n odb/source-packed.c        |   7 ---\n odb/source.h               |  43 +++++----------\n setup.c                    |  61 ++++++++-------------\n setup.h                    |  47 +++++++++++-----\n t/t5604-clone-reference.sh |  25 +++++++++\n 12 files changed, 264 insertions(+), 222 deletions(-)\n\nRange-diff versus v3:\n\n 1:  b0e8d52235 !  1:  e6a17864eb setup: split up concerns of `init_db()`\n    @@ setup.h: int apply_repository_format(struct repository *repo,\n     -\t    enum ref_storage_format ref_storage_format,\n     -\t    const char *initial_branch, int init_shared_repository,\n     -\t    unsigned int flags);\n    ++/*\n    ++ * Create the repository by creating the necessary directory structures,\n    ++ * setting up the configuration and configuring the repository's format. If\n    ++ * `template_dir` is set, copy over templates from that directory. Furthermore,\n    ++ * if and only if `reinit_ok` is a non-NULL pointer, then the function may\n    ++ * reinitialize a preexisting repository. In that case, the pointer will be set\n    ++ * to `1` in case the repo was reinitialized and `0` if it didn't exist yet.\n    ++ *\n    ++ * Note that this function does not create the reference and object databases.\n    ++ */\n     +void create_repository(struct repository *repo,\n     +\t\t       const char *git_dir,\n     +\t\t       const char *real_git_dir,\n    @@ setup.h: int apply_repository_format(struct repository *repo,\n     +\t\t       enum ref_storage_format ref_storage_format,\n     +\t\t       int init_shared_repository,\n     +\t\t       int *reinit_ok);\n    ++\n      void initialize_repository_version(struct repository *repo,\n      \t\t\t\t   int hash_algo,\n      \t\t\t\t   enum ref_storage_format ref_storage_format,\n      \t\t\t\t   int reinit);\n    ++\n    ++/*\n    ++ * Create the reference database for the repository. The repository and its ref\n    ++ * storage format must have already been configured properly before calling\n    ++ * this function. When set, `initial_branch` overrides the default branch that\n    ++ * HEAD will point to.\n    ++ */\n      void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);\n    -+void create_object_database(struct repository *repo);\n      \n    ++/*\n    ++ * Create the object database for the repository. The repository must have\n    ++ * already been configured properly before calling this function.\n    ++ */\n    ++void create_object_database(struct repository *repo);\n    ++\n      /*\n       * NOTE NOTE NOTE!!\n    +  *\n 2:  4fc3a45d2d =  2:  45ae2231ad builtin/clone: defer setup of the object database\n 3:  fd875b60b9 =  3:  92f00f1905 builtin/clone: move around `setup_reference()`\n 4:  1ca710255b =  4:  b6860d41fb builtin/clone: refactor handling of \"--reference{,-if-able}\"\n 5:  08a3a7a682 =  5:  070558b0b8 builtin/clone: move setup of alternates for shared local clones\n 6:  afe84c063b =  6:  8e767a103a builtin/clone: move setup of alternates for non-shared local clones\n 7:  8f0d3b13d5 =  7:  39ee1b828d odb/source: support writing alternates when creating the database\n 8:  75d1e1e34c !  8:  531152ee93 builtin/clone: write alternates via `odb_create_on_disk()`\n    @@ setup.c: static int create_default_files(struct repository *repo,\n      \t * Create the \"objects\" directory in the common directory. This is done\n     \n      ## setup.h ##\n    -@@ setup.h: void initialize_repository_version(struct repository *repo,\n    - \t\t\t\t   enum ref_storage_format ref_storage_format,\n    - \t\t\t\t   int reinit);\n    - void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);\n    +@@ setup.h: void create_reference_database(struct repository *repo, const char *initial_bran\n    + \n    + /*\n    +  * Create the object database for the repository. The repository must have\n    +- * already been configured properly before calling this function.\n    ++ * already been configured properly before calling this function. When set,\n    ++ * `alternates` is the list of alternates that should be written into the\n    ++ * object database.\n    +  */\n     -void create_object_database(struct repository *repo);\n     +void create_object_database(struct repository *repo, const struct strvec *alternates);\n      \n 9:  a674c3af3c =  9:  25bd93544e odb/source: remove the ability to write alternates\n\n---\nbase-commit: afa255aeb620346d56a2c01fb5ae9163513c56d7\nchange-id: 20260813-pks-odb-write-alternates-at-creation-time-64010deb94a0\n\n"},{"id":"552283","messageId":"20260909-pks-odb-write-alternates-at-creation-time-v4-1-d8a78ffc32e4@pks.im","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im","subject":"[PATCH v4 1/9] setup: split up concerns of `init_db()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-09T05:48:43Z","receivedAt":"2026-09-09T05:49:00Z","isPatch":true,"body":"The function `init_db()` is responsible for creating the on-disk\ndirectory structure required for a Git repository. It is used by both\ngit-init(1) and git-clone(1), and because their expected behaviour is\ndifferent we support a couple of flags:\n\n  - The `QUIET` flag controls whether the command is quiet or not. For\n    git-init(1) this is user-controllable, whereas for git-clone(1)\n    we're always quiet.\n\n  - The `EXIST_OK` flag controls whether a preexisting repository is\n    okay or not. For git-init(1) it is, for git-clone(1) it's not.\n\n  - The `SKIP_REFDB` flag controls whether the reference database should\n    already be created or not. For git-init(1) we do, but for\n    git-clone(1) we don't because it does not yet know about the default\n    branch and about the remote object hash.\n\nFurthermore, we're about to add another divergence in behaviour, where\nwe have to also skip creation of the object database in git-clone(1).\nThis is becoming quite cumbersome though.\n\nInstead of introducing another flag, start to split up concerns of the\nfunction so that we never create the reference or object database. This\nbecomes the responsibility of the caller, which is thus free to defer\ntheir creation to a later point in time. This lets us get rid of most of\nthe divergent behaviour:\n\n  - We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags\n    anymore.\n\n  - We don't need the `QUIET` flag anymore, as nothing prints output\n    except for the final status message that tells the user that the\n    repository has been (re)initialized. But as this message is specific\n    to git-init(1), we can easily move it there.\n\nThe only piece of information we still have to convey is whether or not\nreinitialization of a preexisting repository is okay. This is handled by\na new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay\nto reinitialize the repository. Furthermore, the pointer will be written\nto to indicate whether the repository was reinitialized or not, which we\nneed in git-init(1) to print the correct initialization message.\n\nWith these refactorings, `init_db()` is named quite misleadingly though,\nas we don't create any of the reference or object databases anymore.\nRename it to `create_repository()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c   |  9 +++++----\n builtin/init-db.c | 32 ++++++++++++++++++++++++--------\n setup.c           | 54 +++++++++++++++++-------------------------------------\n setup.h           | 45 +++++++++++++++++++++++++++++++++------------\n 4 files changed, 79 insertions(+), 61 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 5b25cca510..904d2d859f 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1185,10 +1185,10 @@ int cmd_clone(int argc,\n \t * repository, and reference backends may persist that information into\n \t * their on-disk data structures.\n \t */\n-\tinit_db(the_repository, git_dir, real_git_dir, work_tree, option_template,\n-\t\tGIT_HASH_UNKNOWN, ref_storage_format, NULL,\n-\t\tdo_not_override_repo_unix_permissions,\n-\t\tINIT_DB_QUIET | INIT_DB_SKIP_REFDB);\n+\tcreate_repository(the_repository, git_dir, real_git_dir, work_tree,\n+\t\t\t  option_template, GIT_HASH_UNKNOWN, ref_storage_format,\n+\t\t\t  do_not_override_repo_unix_permissions, NULL);\n+\tcreate_object_database(the_repository);\n \n \tif (real_git_dir) {\n \t\tfree((char *)git_dir);\n@@ -1445,6 +1445,7 @@ int cmd_clone(int argc,\n \tinitialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);\n \trepo_set_hash_algo(the_repository, hash_algo);\n \tcreate_reference_database(the_repository, NULL, 1);\n+\tstartup_info->have_repository = 1;\n \n \t/*\n \t * Before fetching from the remote, download and install bundle\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex e96b1283b7..f2c7e3be6d 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -80,7 +80,7 @@ int cmd_init_db(int argc,\n \tchar *work_tree = NULL;\n \tconst char *template_dir = NULL;\n \tchar *template_dir_to_free = NULL;\n-\tunsigned int flags = 0;\n+\tint quiet = 0;\n \tint bare = startup_info->force_bare_repository ? 1 : -1;\n \tconst char *object_format = NULL;\n \tconst char *ref_format = NULL;\n@@ -102,7 +102,7 @@ int cmd_init_db(int argc,\n \t\t\t.flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG,\n \t\t\t.callback = shared_callback\n \t\t},\n-\t\tOPT_BIT('q', \"quiet\", &flags, N_(\"be quiet\"), INIT_DB_QUIET),\n+\t\tOPT_BOOL('q', \"quiet\", &quiet, N_(\"be quiet\")),\n \t\tOPT_STRING(0, \"separate-git-dir\", &real_git_dir, N_(\"gitdir\"),\n \t\t\t   N_(\"separate git dir from working tree\")),\n \t\tOPT_STRING('b', \"initial-branch\", &initial_branch, N_(\"name\"),\n@@ -113,7 +113,7 @@ int cmd_init_db(int argc,\n \t\t\t   N_(\"specify the reference format to use\")),\n \t\tOPT_END()\n \t};\n-\tint ret;\n+\tint reinit;\n \n \targc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);\n \n@@ -247,14 +247,30 @@ int cmd_init_db(int argc,\n \t\tdie(_(\"--separate-git-dir incompatible with bare repository\"));\n \t}\n \n-\tflags |= INIT_DB_EXIST_OK;\n-\tret = init_db(the_repository, git_dir, real_git_dir, work_tree,\n-\t\t      template_dir, hash_algo, ref_storage_format, initial_branch,\n-\t\t      init_shared_repository, flags);\n+\tcreate_repository(the_repository, git_dir, real_git_dir, work_tree,\n+\t\t\t  template_dir, hash_algo, ref_storage_format,\n+\t\t\t  init_shared_repository, &reinit);\n+\tcreate_reference_database(the_repository, initial_branch, quiet);\n+\tcreate_object_database(the_repository);\n+\n+\tif (!quiet) {\n+\t\tint len = strlen(git_dir);\n+\n+\t\tif (reinit)\n+\t\t\tprintf(repo_settings_get_shared_repository(the_repository)\n+\t\t\t       ? _(\"Reinitialized existing shared Git repository in %s%s\\n\")\n+\t\t\t       : _(\"Reinitialized existing Git repository in %s%s\\n\"),\n+\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n+\t\telse\n+\t\t\tprintf(repo_settings_get_shared_repository(the_repository)\n+\t\t\t       ? _(\"Initialized empty shared Git repository in %s%s\\n\")\n+\t\t\t       : _(\"Initialized empty Git repository in %s%s\\n\"),\n+\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n+\t}\n \n \tfree(template_dir_to_free);\n \tfree(real_git_dir_to_free);\n \tfree(work_tree);\n \tfree(git_dir);\n-\treturn ret;\n+\treturn 0;\n }\ndiff --git a/setup.c b/setup.c\nindex d90654f584..8c7b97f92e 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,\n \treturn reinit;\n }\n \n-static void create_object_database(struct repository *repo)\n+void create_object_database(struct repository *repo)\n {\n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\n@@ -2822,17 +2822,17 @@ static void repository_format_configure(struct repository_format *repo_fmt,\n \t}\n }\n \n-int init_db(struct repository *repo,\n-\t    const char *git_dir,\n-\t    const char *real_git_dir,\n-\t    const char *worktree,\n-\t    const char *template_dir, int hash,\n-\t    enum ref_storage_format ref_storage_format,\n-\t    const char *initial_branch,\n-\t    int init_shared_repository, unsigned int flags)\n+void create_repository(struct repository *repo,\n+\t\t       const char *git_dir,\n+\t\t       const char *real_git_dir,\n+\t\t       const char *worktree,\n+\t\t       const char *template_dir,\n+\t\t       int hash,\n+\t\t       enum ref_storage_format ref_storage_format,\n+\t\t       int init_shared_repository,\n+\t\t       int *reinit_ok)\n {\n-\tint reinit;\n-\tint exist_ok = flags & INIT_DB_EXIST_OK;\n+\tint reinit_ignored;\n \tchar *original_git_dir = real_pathdup(git_dir, 1);\n \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2840,10 +2840,10 @@ int init_db(struct repository *repo,\n \tif (real_git_dir) {\n \t\tstruct stat st;\n \n-\t\tif (!exist_ok && !stat(git_dir, &st))\n+\t\tif (!reinit_ok && !stat(git_dir, &st))\n \t\t\tdie(_(\"%s already exists\"), git_dir);\n \n-\t\tif (!exist_ok && !stat(real_git_dir, &st))\n+\t\tif (!reinit_ok && !stat(real_git_dir, &st))\n \t\t\tdie(_(\"%s already exists\"), real_git_dir);\n \n \t\tapply_and_export_relative_gitdir(repo, real_git_dir, 1);\n@@ -2877,8 +2877,10 @@ int init_db(struct repository *repo,\n \n \tsafe_create_dir(repo, git_dir, 0);\n \n-\treinit = create_default_files(repo, template_dir, original_git_dir,\n-\t\t\t\t      &repo_fmt, init_shared_repository);\n+\tif (!reinit_ok)\n+\t\treinit_ok = &reinit_ignored;\n+\t*reinit_ok = create_default_files(repo, template_dir, original_git_dir,\n+\t\t\t\t\t  &repo_fmt, init_shared_repository);\n \n \tif (repo_settings_get_shared_repository(repo)) {\n \t\tchar buf[10];\n@@ -2901,29 +2903,7 @@ int init_db(struct repository *repo,\n \t\trepo_config_set(repo, \"receive.denyNonFastforwards\", \"true\");\n \t}\n \n-\tif (!(flags & INIT_DB_SKIP_REFDB))\n-\t\tcreate_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);\n-\tcreate_object_database(repo);\n-\n-\tstartup_info->have_repository = 1;\n-\n-\tif (!(flags & INIT_DB_QUIET)) {\n-\t\tint len = strlen(git_dir);\n-\n-\t\tif (reinit)\n-\t\t\tprintf(repo_settings_get_shared_repository(repo)\n-\t\t\t       ? _(\"Reinitialized existing shared Git repository in %s%s\\n\")\n-\t\t\t       : _(\"Reinitialized existing Git repository in %s%s\\n\"),\n-\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n-\t\telse\n-\t\t\tprintf(repo_settings_get_shared_repository(repo)\n-\t\t\t       ? _(\"Initialized empty shared Git repository in %s%s\\n\")\n-\t\t\t       : _(\"Initialized empty Git repository in %s%s\\n\"),\n-\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n-\t}\n-\n \tclear_repository_format(&repo_fmt);\n \tstrbuf_release(&err);\n \tfree(original_git_dir);\n-\treturn 0;\n }\ndiff --git a/setup.h b/setup.h\nindex 763fd384e8..f1c1ed65fb 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -256,24 +256,45 @@ int apply_repository_format(struct repository *repo,\n \n const char *get_template_dir(const char *option_template);\n \n-#define INIT_DB_QUIET      (1 << 0)\n-#define INIT_DB_EXIST_OK   (1 << 1)\n-#define INIT_DB_SKIP_REFDB (1 << 2)\n-\n-int init_db(struct repository *repo,\n-\t    const char *git_dir,\n-\t    const char *real_git_dir,\n-\t    const char *worktree,\n-\t    const char *template_dir, int hash_algo,\n-\t    enum ref_storage_format ref_storage_format,\n-\t    const char *initial_branch, int init_shared_repository,\n-\t    unsigned int flags);\n+/*\n+ * Create the repository by creating the necessary directory structures,\n+ * setting up the configuration and configuring the repository's format. If\n+ * `template_dir` is set, copy over templates from that directory. Furthermore,\n+ * if and only if `reinit_ok` is a non-NULL pointer, then the function may\n+ * reinitialize a preexisting repository. In that case, the pointer will be set\n+ * to `1` in case the repo was reinitialized and `0` if it didn't exist yet.\n+ *\n+ * Note that this function does not create the reference and object databases.\n+ */\n+void create_repository(struct repository *repo,\n+\t\t       const char *git_dir,\n+\t\t       const char *real_git_dir,\n+\t\t       const char *worktree,\n+\t\t       const char *template_dir,\n+\t\t       int hash_algo,\n+\t\t       enum ref_storage_format ref_storage_format,\n+\t\t       int init_shared_repository,\n+\t\t       int *reinit_ok);\n+\n void initialize_repository_version(struct repository *repo,\n \t\t\t\t   int hash_algo,\n \t\t\t\t   enum ref_storage_format ref_storage_format,\n \t\t\t\t   int reinit);\n+\n+/*\n+ * Create the reference database for the repository. The repository and its ref\n+ * storage format must have already been configured properly before calling\n+ * this function. When set, `initial_branch` overrides the default branch that\n+ * HEAD will point to.\n+ */\n void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);\n \n+/*\n+ * Create the object database for the repository. The repository must have\n+ * already been configured properly before calling this function.\n+ */\n+void create_object_database(struct repository *repo);\n+\n /*\n  * NOTE NOTE NOTE!!\n  *\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552284","messageId":"20260909-pks-odb-write-alternates-at-creation-time-v4-2-d8a78ffc32e4@pks.im","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im","subject":"[PATCH v4 2/9] builtin/clone: defer setup of the object database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-09T05:48:44Z","receivedAt":"2026-09-09T05:49:01Z","isPatch":true,"body":"When cloning a repository we defer initialization of the reference\ndatabase. This is because we don't yet know all details required for us\nto initialize the refdb in the first place. Most importantly, what we\nare missing is information about the object hash.\n\nWe don't do the same thing for the object database yet, but here we\nessentially have the same problem. While the \"files\" database does not\nneed any information about the object format at creation time, alternate\nbackends are likely to require that information so that they can\nproperly set up their data structures.\n\nBesides this forward-looking future proofing though, we also have a\nsecond use case for deferring initialization of the object database,\nnamely alternates. When initializing the object database we do not yet\nknow whether we'll need alternates or not because this depends on the\nrepository we're about to clone from. If it is a local repository and\nthe user has passed \"--refernce{,-if-able}\", then we will end up writing\nalternates into the object database.\n\nThe ugly part though is that we cannot determine where the repository is\ngetting cloned from before it has been initialized. While we of course\nalready have access to the user-provided URI, that URI can be very well\nrewritten via \"url.<base>.insteadOf\". We can of course read the global-\nand system-level configuration to resolve it. But we explicitly resolve\nthe URI a second time after we have initialized the repository because\nit can happen that we copy a \".git/config\" over from our templates, and\nthat file may cause us to rewrite the path.\n\nIn a subsequent commit though we'll start to write alternates as part of\nthe repository initialization, so we'll need to have the URI properly\nresolved before we can initialize the object database. This is ugly, but\nas mentioned above it makes sense for us to defer its initialization\nanyway so that we also know about the object hash already.\n\nDefer creation of the object database until after we have resolved the\nURI.\n\nNote that this also requires us to defer the call to `setup_reference()`\nuntil after we have created the object database. While you might think\nthat this function has something to do with references (\"refs/*\"), it is\nin fact responsible for setting up alternates. Consequently, we can only\ncall it after we have created the object database already.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 904d2d859f..bdcbd7aa1b 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1188,7 +1188,6 @@ int cmd_clone(int argc,\n \tcreate_repository(the_repository, git_dir, real_git_dir, work_tree,\n \t\t\t  option_template, GIT_HASH_UNKNOWN, ref_storage_format,\n \t\t\t  do_not_override_repo_unix_permissions, NULL);\n-\tcreate_object_database(the_repository);\n \n \tif (real_git_dir) {\n \t\tfree((char *)git_dir);\n@@ -1311,9 +1310,6 @@ int cmd_clone(int argc,\n \t\tstrbuf_reset(&key);\n \t}\n \n-\tif (option_required_reference.nr || option_optional_reference.nr)\n-\t\tsetup_reference();\n-\n \tremote = remote_get_early(remote_name);\n \n \tif (!option_rev)\n@@ -1342,6 +1338,10 @@ int cmd_clone(int argc,\n \tif (option_local > 0 && !is_local)\n \t\twarning(_(\"--local is ignored\"));\n \n+\tcreate_object_database(the_repository);\n+\tif (option_required_reference.nr || option_optional_reference.nr)\n+\t\tsetup_reference();\n+\n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\n \ttransport->family = family;\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552285","messageId":"20260909-pks-odb-write-alternates-at-creation-time-v4-3-d8a78ffc32e4@pks.im","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im","subject":"[PATCH v4 3/9] builtin/clone: move around `setup_reference()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-09T05:48:45Z","receivedAt":"2026-09-09T05:49:04Z","isPatch":true,"body":"In a subsequent commit, `setup_reference()` will start to call\n`copy_alternates()`. Prepare for this by moving the function further\ndown so that we can avoid adding a declaration.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex bdcbd7aa1b..ac5843d7b9 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)\n \treturn 0;\n }\n \n-static void setup_reference(void)\n-{\n-\tint required = 1;\n-\tfor_each_string_list(&option_required_reference,\n-\t\t\t     add_one_reference, &required);\n-\trequired = 0;\n-\tfor_each_string_list(&option_optional_reference,\n-\t\t\t     add_one_reference, &required);\n-}\n-\n static void copy_alternates(struct strbuf *src, const char *src_repo)\n {\n \t/*\n@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n+static void setup_reference(void)\n+{\n+\tint required = 1;\n+\tfor_each_string_list(&option_required_reference,\n+\t\t\t     add_one_reference, &required);\n+\trequired = 0;\n+\tfor_each_string_list(&option_optional_reference,\n+\t\t\t     add_one_reference, &required);\n+}\n+\n static void mkdir_if_missing(const char *pathname, mode_t mode)\n {\n \tstruct stat st;\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552286","messageId":"20260909-pks-odb-write-alternates-at-creation-time-v4-4-d8a78ffc32e4@pks.im","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im","subject":"[PATCH v4 4/9] builtin/clone: refactor handling of \"--reference{,-if-able}\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-09T05:48:46Z","receivedAt":"2026-09-09T05:49:06Z","isPatch":true,"body":"Users can pass \"--reference{,-if-able}\" to git-clone(1) to instruct it\nto set up alternates for the newly created repository. This allows it to\nreuse objects from the source repository so that in the best case we\ndon't have to clone all objects over.\n\nThose options are handled by the confusingly named `setup_reference()`\nfunction -- without the above context, one might rightfully believe that\nit was about refs, not about alternates. The function itself is rather\nsimple: we loop through all provided alternate paths and then, if such\nan alternate is valid, we write it to the object database.\n\nIn subsequent commits we're about to consolidate the complete setup of\nalternates into this function, and furthermore we'll refactor the setup\nof the object database to handle doing this for us instead of writing\nthe alternates into it one by one.\n\nPrepare for this refactoring by collecting the alternates into a strvec.\nRename the function to `collect_alternates()` to clarify its scope.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------\n 1 file changed, 28 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex ac5843d7b9..8786a49332 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)\n \treturn canon;\n }\n \n-static int add_one_reference(struct string_list_item *item, void *cb_data)\n+struct add_one_alternate_data {\n+\tstruct strvec *alternates;\n+\tint required;\n+};\n+\n+static int add_one_alternate(struct string_list_item *item, void *cb_data)\n {\n+\tstruct add_one_alternate_data *data = cb_data;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint *required = cb_data;\n \tchar *ref_git = compute_alternate_path(item->string, &err);\n \n \tif (!ref_git) {\n-\t\tif (*required)\n+\t\tif (data->required)\n \t\t\tdie(\"%s\", err.buf);\n \t\telse\n \t\t\tfprintf(stderr,\n \t\t\t\t_(\"info: Could not add alternate for '%s': %s\\n\"),\n \t\t\t\titem->string, err.buf);\n \t} else {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\tstrbuf_addf(&sb, \"%s/objects\", ref_git);\n-\t\todb_add_to_alternates_file(the_repository->objects, sb.buf);\n-\t\tstrbuf_release(&sb);\n+\t\tstrvec_pushf(data->alternates, \"%s/objects\", ref_git);\n \t}\n \n \tstrbuf_release(&err);\n@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n-static void setup_reference(void)\n+static void collect_alternates(struct strvec *alternates)\n {\n-\tint required = 1;\n-\tfor_each_string_list(&option_required_reference,\n-\t\t\t     add_one_reference, &required);\n-\trequired = 0;\n-\tfor_each_string_list(&option_optional_reference,\n-\t\t\t     add_one_reference, &required);\n+\tif (option_required_reference.nr || option_optional_reference.nr) {\n+\t\tstruct add_one_alternate_data data = {\n+\t\t\t.alternates = alternates,\n+\t\t\t.required = 1,\n+\t\t};\n+\n+\t\tfor_each_string_list(&option_required_reference,\n+\t\t\t\t     add_one_alternate, &data);\n+\t\tdata.required = 0;\n+\t\tfor_each_string_list(&option_optional_reference,\n+\t\t\t\t     add_one_alternate, &data);\n+\t}\n }\n \n static void mkdir_if_missing(const char *pathname, mode_t mode)\n@@ -999,6 +1007,7 @@ int cmd_clone(int argc,\n \t\t\t   N_(\"uri\"), N_(\"a URI for downloading bundles before fetching from origin remote\")),\n \t\tOPT_END()\n \t};\n+\tstruct strvec alternates = STRVEC_INIT;\n \n \tconst char * const builtin_clone_usage[] = {\n \t\tN_(\"git clone [<options>] [--] <repo> [<dir>]\"),\n@@ -1339,8 +1348,10 @@ int cmd_clone(int argc,\n \t\twarning(_(\"--local is ignored\"));\n \n \tcreate_object_database(the_repository);\n-\tif (option_required_reference.nr || option_optional_reference.nr)\n-\t\tsetup_reference();\n+\tcollect_alternates(&alternates);\n+\n+\tfor (size_t i = 0; i < alternates.nr; i++)\n+\t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n \n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\n@@ -1638,6 +1649,7 @@ int cmd_clone(int argc,\n \tstring_list_clear(&option_not, 0);\n \tstring_list_clear(&option_config, 0);\n \tstring_list_clear(&server_options, 0);\n+\tstrvec_clear(&alternates);\n \n \tfree(remote_name);\n \tstrbuf_release(&reflog_msg);\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552287","messageId":"20260909-pks-odb-write-alternates-at-creation-time-v4-5-d8a78ffc32e4@pks.im","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im","subject":"[PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-09T05:48:47Z","receivedAt":"2026-09-09T05:49:09Z","isPatch":true,"body":"When cloning a local repository with \"--shared\" we add that repository\nto the new repository's alternates. This is done in `clone_local()`,\nwhich is responsible for performing local clones.\n\nMove the logic into `collect_alternates()` to unify our setup of\nalternates. Furthermore, this will allow us to set up alternates right\nat creation time of the object database.\n\nNote that the logic for cloning a local repository with \"--no-shared\" is\nnot yet part of `collect_alternates()`. This will be handled in the next\ncommit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 23 ++++++++++++++---------\n 1 file changed, 14 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 8786a49332..011fc867c8 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n-static void collect_alternates(struct strvec *alternates)\n+static void collect_alternates(struct strvec *alternates,\n+\t\t\t       const char *src_repo, bool is_local)\n {\n \tif (option_required_reference.nr || option_optional_reference.nr) {\n \t\tstruct add_one_alternate_data data = {\n@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)\n \t\tfor_each_string_list(&option_optional_reference,\n \t\t\t\t     add_one_alternate, &data);\n \t}\n+\n+\tif (is_local) {\n+\t\tstruct strbuf commondir = STRBUF_INIT;\n+\n+\t\tget_common_dir(&commondir, src_repo);\n+\t\tif (option_shared)\n+\t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n+\n+\t\tstrbuf_release(&commondir);\n+\t}\n }\n \n static void mkdir_if_missing(const char *pathname, mode_t mode)\n@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,\n \n static void clone_local(const char *src_repo, const char *dest_repo)\n {\n-\tif (option_shared) {\n-\t\tstruct strbuf alt = STRBUF_INIT;\n-\t\tget_common_dir(&alt, src_repo);\n-\t\tstrbuf_addstr(&alt, \"/objects\");\n-\t\todb_add_to_alternates_file(the_repository->objects, alt.buf);\n-\t\tstrbuf_release(&alt);\n-\t} else {\n+\tif (!option_shared) {\n \t\tstruct strbuf src = STRBUF_INIT;\n \t\tstruct strbuf dest = STRBUF_INIT;\n \t\tget_common_dir(&src, src_repo);\n@@ -1348,7 +1353,7 @@ int cmd_clone(int argc,\n \t\twarning(_(\"--local is ignored\"));\n \n \tcreate_object_database(the_repository);\n-\tcollect_alternates(&alternates);\n+\tcollect_alternates(&alternates, path, is_local);\n \n \tfor (size_t i = 0; i < alternates.nr; i++)\n \t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552288","messageId":"20260909-pks-odb-write-alternates-at-creation-time-v4-6-d8a78ffc32e4@pks.im","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im","subject":"[PATCH v4 6/9] builtin/clone: move setup of alternates for non-shared local clones","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-09T05:48:48Z","receivedAt":"2026-09-09T05:49:11Z","isPatch":true,"body":"Similar as in the preceding commit, move the setup of alternates for\nlocal clones with \"--no-shared\" into `collect_alternates()`. With this\nstep, the complete setup of alternates is now handled by that function.\n\nNote that besides moving stuff around, it also fixes a bug: previously,\nwe did not know to resolve the referenced repository's common directory.\nConsequently, when referencing a worktree we failed to resolve\nalternates. But as `collect_alternates()` already knows to resolve the\ncommondir for \"--local\" we can simply reuse this resolved path for our\npurpose.\n\nAdd two tests, the first one of which exercises this bug to avoid future\nregressions. The second test ensures that we properly handle relative\nalternates for a referenced worktree.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c            | 34 +++++++++++++++++++++++-----------\n t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 48 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 011fc867c8..84c1317867 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)\n \treturn 0;\n }\n \n-static void copy_alternates(struct strbuf *src, const char *src_repo)\n+static void read_alternates(struct strvec *alternates, const char *src_repo)\n {\n \t/*\n \t * Read from the source objects/info/alternates file\n@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \t * to turn entries with paths relative to the original\n \t * absolute, so that they can be used in the new repository.\n \t */\n-\tFILE *in = xfopen(src->buf, \"r\");\n+\tFILE *in;\n+\tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf line = STRBUF_INIT;\n \n+\tstrbuf_addf(&path, \"%s/objects/info/alternates\", src_repo);\n+\n+\tin = fopen(path.buf, \"r\");\n+\tif (!in) {\n+\t\tif (errno == ENOENT)\n+\t\t\tgoto out;\n+\t\tdie_errno(\"could not read alternates file '%s'\", path.buf);\n+\t}\n+\n \twhile (strbuf_getline(&line, in) != EOF) {\n \t\tchar *abs_path;\n \t\tif (!line.len || line.buf[0] == '#')\n \t\t\tcontinue;\n \t\tif (is_absolute_path(line.buf)) {\n-\t\t\todb_add_to_alternates_file(the_repository->objects,\n-\t\t\t\t\t\t   line.buf);\n+\t\t\tstrvec_push(alternates, line.buf);\n \t\t\tcontinue;\n \t\t}\n \t\tabs_path = mkpathdup(\"%s/objects/%s\", src_repo, line.buf);\n \t\tif (!normalize_path_copy(abs_path, abs_path))\n-\t\t\todb_add_to_alternates_file(the_repository->objects,\n-\t\t\t\t\t\t   abs_path);\n+\t\t\tstrvec_push(alternates, abs_path);\n \t\telse\n \t\t\twarning(\"skipping invalid relative alternate: %s/%s\",\n \t\t\t\tsrc_repo, line.buf);\n \t\tfree(abs_path);\n \t}\n+\n+out:\n+\tstrbuf_release(&path);\n \tstrbuf_release(&line);\n-\tfclose(in);\n+\tif (in)\n+\t\tfclose(in);\n }\n \n static void collect_alternates(struct strvec *alternates,\n@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,\n \t\tget_common_dir(&commondir, src_repo);\n \t\tif (option_shared)\n \t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n+\t\telse\n+\t\t\tread_alternates(alternates, commondir.buf);\n \n \t\tstrbuf_release(&commondir);\n \t}\n@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,\n \t\t\tcontinue;\n \t\t}\n \n-\t\t/* Files that cannot be copied bit-for-bit... */\n-\t\tif (!fspathcmp(iter->relative_path, \"info/alternates\")) {\n-\t\t\tcopy_alternates(src, src_repo);\n+\t\t/* Alternates were already handled earlier. */\n+\t\tif (!fspathcmp(iter->relative_path, \"info/alternates\"))\n \t\t\tcontinue;\n-\t\t}\n \n \t\tif (unlink(dest->buf) && errno != ENOENT)\n \t\t\tdie_errno(_(\"failed to unlink '%s'\"), dest->buf);\ndiff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh\nindex 39a0c318df..9e4b98fdb8 100755\n--- a/t/t5604-clone-reference.sh\n+++ b/t/t5604-clone-reference.sh\n@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '\n \tgit clone --no-local --reference graph.git --dissociate orig clone\n '\n \n+test_expect_success 'local clone from linked worktree carries over alternates' '\n+\trm -fr base derived derived-wt dst expect &&\n+\tgit init base &&\n+\ttest_commit -C base one &&\n+\tgit clone --shared base derived &&\n+\tgit -C derived worktree add ../derived-wt &&\n+\tgit clone derived-wt dst &&\n+\techo \"$(pwd)/base/.git/objects\" >expect &&\n+\ttest_cmp expect dst/.git/objects/info/alternates &&\n+\tgit -C dst fsck\n+'\n+\n+test_expect_success 'local clone from linked worktree resolves relative alternates' '\n+\trm -fr base derived derived-wt dst expect &&\n+\tgit init base &&\n+\ttest_commit -C base one &&\n+\tgit clone --shared base derived &&\n+\techo \"../../../base/.git/objects\" >derived/.git/objects/info/alternates &&\n+\tgit -C derived worktree add ../derived-wt &&\n+\tgit clone derived-wt dst &&\n+\techo \"$(pwd)/base/.git/objects\" >expect &&\n+\ttest_cmp expect dst/.git/objects/info/alternates &&\n+\tgit -C dst fsck\n+'\n+\n test_done\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552289","messageId":"20260909-pks-odb-write-alternates-at-creation-time-v4-7-d8a78ffc32e4@pks.im","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im","subject":"[PATCH v4 7/9] odb/source: support writing alternates when creating the database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-09T05:48:49Z","receivedAt":"2026-09-09T05:49:15Z","isPatch":true,"body":"Add the ability to write alternates when creating the object database.\nThis change allows us to remove the `write_alternates()` callback in a\nsubsequent patch.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--\n odb/source.h       | 17 +++++++++---\n setup.c            |  4 ++-\n 3 files changed, 91 insertions(+), 6 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex b7b3a297bb..8fe65d91f8 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -18,6 +18,7 @@\n #include \"run-command.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n+#include \"strmap.h\"\n #include \"strvec.h\"\n #include \"tree.h\"\n #include \"write-or-die.h\"\n@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)\n \todb_source_close(&files->packed->base);\n }\n \n-static int odb_source_files_create_on_disk(struct odb_source *source)\n+static int odb_source_files_create_on_disk(struct odb_source *source,\n+\t\t\t\t\t   const struct odb_create_on_disk_options *opts)\n {\n+\tstruct lock_file alternates_lock = LOCK_INIT;\n \tstruct strbuf path = STRBUF_INIT;\n+\tstruct strset seen = STRSET_INIT;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tint ret;\n \n \tsafe_create_dir(source->odb->repo, source->path, 1);\n \n@@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)\n \tstrbuf_addf(&path, \"%s/info\", source->path);\n \tsafe_create_dir(source->odb->repo, path.buf, 1);\n \n+\tif (opts->alternates && opts->alternates->nr) {\n+\t\tFILE *alternates, *orig;\n+\n+\t\tstrbuf_reset(&path);\n+\t\tstrbuf_addf(&path, \"%s/info/alternates\", source->path);\n+\n+\t\trepo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,\n+\t\t\t\t\t       path.buf, LOCK_DIE_ON_ERROR);\n+\n+\t\talternates = fdopen_lock_file(&alternates_lock, \"w\");\n+\t\tif (!alternates) {\n+\t\t\tret = error_errno(_(\"unable to fdopen alternates lockfile\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/*\n+\t\t * The alternates file may already exist, e.g. when it has been\n+\t\t * seeded from a template directory. Read any preexisting\n+\t\t * entries so that we don't end up writing duplicates.\n+\t\t */\n+\t\torig = fopen(path.buf, \"r\");\n+\t\tif (orig) {\n+\t\t\twhile (strbuf_getline(&line, orig) != EOF) {\n+\t\t\t\tstrset_add(&seen, line.buf);\n+\t\t\t\tfprintf(alternates, \"%s\\n\", line.buf);\n+\t\t\t}\n+\n+\t\t\tif (ferror(orig)) {\n+\t\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n+\t\t\t\tfclose(orig);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\tfclose(orig);\n+\t\t} else if (errno != ENOENT) {\n+\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (size_t i = 0; i < opts->alternates->nr; i++) {\n+\t\t\tconst char *alternate = opts->alternates->v[i];\n+\t\t\tif (!strset_add(&seen, alternate))\n+\t\t\t\tcontinue;\n+\t\t\tfprintf(alternates, \"%s\\n\", alternate);\n+\t\t}\n+\n+\t\tif (ferror(alternates)) {\n+\t\t\tret = error_errno(_(\"unable to write alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (commit_lock_file(&alternates_lock)) {\n+\t\t\tret = error_errno(_(\"unable to commit alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+\t/* Reprepare the object database to activate alternates. */\n+\todb_reprepare(source->odb);\n+\n+\tret = 0;\n+\n+out:\n+\trollback_lock_file(&alternates_lock);\n+\tstrbuf_release(&line);\n \tstrbuf_release(&path);\n-\treturn 0;\n+\tstrset_clear(&seen);\n+\treturn ret;\n }\n \n static void odb_source_files_prepare(struct odb_source *source,\ndiff --git a/odb/source.h b/odb/source.h\nindex ea8675247e..63f1c0c531 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -36,6 +36,15 @@ struct object_id;\n struct odb_stream;\n struct strvec;\n \n+struct odb_create_on_disk_options {\n+\t/*\n+\t * Alternates that shall be written into the newly created object\n+\t * database. Whether or not this option can be handled is specific to\n+\t * the backend.\n+\t */\n+\tconst struct strvec *alternates;\n+};\n+\n /*\n  * The source is the part of the object database that stores the actual\n  * objects. It thus encapsulates the logic to read and write the specific\n@@ -106,7 +115,8 @@ struct odb_source {\n \t * This callback may be NULL in case the source does not need any\n \t * on-disk setup.\n \t */\n-\tint (*create_on_disk)(struct odb_source *source);\n+\tint (*create_on_disk)(struct odb_source *source,\n+\t\t\t      const struct odb_create_on_disk_options *opts);\n \n \t/*\n \t * This callback is expected to prepare the source so that it becomes\n@@ -356,11 +366,12 @@ static inline void odb_source_close(struct odb_source *source)\n  * Create on-disk data structures that are required for this source to operate\n  * correctly. Returns 0 on success, a negative error code otherwise.\n  */\n-static inline int odb_source_create_on_disk(struct odb_source *source)\n+static inline int odb_source_create_on_disk(struct odb_source *source,\n+\t\t\t\t\t    const struct odb_create_on_disk_options *opts)\n {\n \tif (!source->create_on_disk)\n \t\treturn 0;\n-\treturn source->create_on_disk(source);\n+\treturn source->create_on_disk(source, opts);\n }\n \n /*\ndiff --git a/setup.c b/setup.c\nindex 8c7b97f92e..37a8e6f124 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,\n \n void create_object_database(struct repository *repo)\n {\n+\tstruct odb_create_on_disk_options opts = { 0 };\n+\n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\n \t * so that the repository can be discovered regardless of the backend\n@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)\n \n \trepo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);\n \n-\tif (odb_source_create_on_disk(repo->objects->sources) < 0)\n+\tif (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)\n \t\tdie(_(\"failed creating object database\"));\n }\n \n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552290","messageId":"20260909-pks-odb-write-alternates-at-creation-time-v4-8-d8a78ffc32e4@pks.im","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im","subject":"[PATCH v4 8/9] builtin/clone: write alternates via `odb_create_on_disk()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-09T05:48:50Z","receivedAt":"2026-09-09T05:49:17Z","isPatch":true,"body":"When creating a repository with alternates we first initialize the\nobject database and then write alternates to it in a separate step. This\nis unfortunate due to a couple of reasons:\n\n  - It requires us to have a `write_alternates()` callback, which is\n    unfortunate as we never even write alternates to an object database\n    after it has been created.\n\n  - We're about to make alternates an implementation detail of the\n    object database's backend in a future patch series, so having this\n    callback is suboptimal there.\n\n  - The backend has more flexibility with how exactly alternates are\n    configured when it itself is in full control over their setup at the\n    time where it creates the object database itself.\n\nWe have thus introduced the ability to write alternates right at\ncreation time in the preceding commits, and we have unified setup of\nalternates into a single location. All that's left to do for us now is\nto wire up alternates as an option for the database creation.\n\nDo so.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c   | 5 +----\n builtin/init-db.c | 2 +-\n setup.c           | 7 +++++--\n setup.h           | 6 ++++--\n 4 files changed, 11 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 84c1317867..9e84646845 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1364,11 +1364,8 @@ int cmd_clone(int argc,\n \tif (option_local > 0 && !is_local)\n \t\twarning(_(\"--local is ignored\"));\n \n-\tcreate_object_database(the_repository);\n \tcollect_alternates(&alternates, path, is_local);\n-\n-\tfor (size_t i = 0; i < alternates.nr; i++)\n-\t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n+\tcreate_object_database(the_repository, &alternates);\n \n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex f2c7e3be6d..5c22eae2f3 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -251,7 +251,7 @@ int cmd_init_db(int argc,\n \t\t\t  template_dir, hash_algo, ref_storage_format,\n \t\t\t  init_shared_repository, &reinit);\n \tcreate_reference_database(the_repository, initial_branch, quiet);\n-\tcreate_object_database(the_repository);\n+\tcreate_object_database(the_repository, NULL);\n \n \tif (!quiet) {\n \t\tint len = strlen(git_dir);\ndiff --git a/setup.c b/setup.c\nindex 37a8e6f124..17d0d25973 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,\n \treturn reinit;\n }\n \n-void create_object_database(struct repository *repo)\n+void create_object_database(struct repository *repo,\n+\t\t\t    const struct strvec *alternates)\n {\n-\tstruct odb_create_on_disk_options opts = { 0 };\n+\tstruct odb_create_on_disk_options opts = {\n+\t\t.alternates = alternates,\n+\t};\n \n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\ndiff --git a/setup.h b/setup.h\nindex f1c1ed65fb..27b2492373 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -291,9 +291,11 @@ void create_reference_database(struct repository *repo, const char *initial_bran\n \n /*\n  * Create the object database for the repository. The repository must have\n- * already been configured properly before calling this function.\n+ * already been configured properly before calling this function. When set,\n+ * `alternates` is the list of alternates that should be written into the\n+ * object database.\n  */\n-void create_object_database(struct repository *repo);\n+void create_object_database(struct repository *repo, const struct strvec *alternates);\n \n /*\n  * NOTE NOTE NOTE!!\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552291","messageId":"20260909-pks-odb-write-alternates-at-creation-time-v4-9-d8a78ffc32e4@pks.im","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im","subject":"[PATCH v4 9/9] odb/source: remove the ability to write alternates","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-09T05:48:51Z","receivedAt":"2026-09-09T05:49:20Z","isPatch":true,"body":"There are no users of `odb_source_write_alternates()` in our tree\nanymore. Remove that function and its supporting infrastructure.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c                 |  9 ---------\n odb.h                 |  7 -------\n odb/source-files.c    | 54 ---------------------------------------------------\n odb/source-inmemory.c |  7 -------\n odb/source-loose.c    |  7 -------\n odb/source-packed.c   |  7 -------\n odb/source.h          | 26 -------------------------\n 7 files changed, 117 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 67d98d64fc..b531cf8fb3 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *\n \treturn alternate;\n }\n \n-void odb_add_to_alternates_file(struct object_database *odb,\n-\t\t\t\tconst char *dir)\n-{\n-\tint ret = odb_source_write_alternate(odb->sources, dir);\n-\tif (ret < 0)\n-\t\tdie(NULL);\n-\todb_add_alternate_recursively(odb, dir, 0);\n-}\n-\n struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,\n \t\t\t\t\t\tconst char *dir)\n {\ndiff --git a/odb.h b/odb.h\nindex b9e0db56ec..2d002461f8 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,\n  */\n int odb_has_alternates(struct object_database *odb);\n \n-/*\n- * Add the directory to the on-disk alternates file; the new entry will also\n- * take effect in the current process.\n- */\n-void odb_add_to_alternates_file(struct object_database *odb,\n-\t\t\t\tconst char *dir);\n-\n /*\n  * Add the directory to the in-memory list of alternate sources (along with any\n  * recursive alternates it points to), but do not modify the on-disk alternates\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 8fe65d91f8..b3f340dff8 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -306,59 +306,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,\n \treturn 0;\n }\n \n-static int odb_source_files_write_alternate(struct odb_source *source,\n-\t\t\t\t\t    const char *alternate)\n-{\n-\tstruct lock_file lock = LOCK_INIT;\n-\tchar *path = xstrfmt(\"%s/%s\", source->path, \"info/alternates\");\n-\tFILE *in, *out;\n-\tint found = 0;\n-\tint ret;\n-\n-\trepo_hold_lock_file_for_update(source->odb->repo, &lock, path,\n-\t\t\t\t       LOCK_DIE_ON_ERROR);\n-\tout = fdopen_lock_file(&lock, \"w\");\n-\tif (!out) {\n-\t\tret = error_errno(_(\"unable to fdopen alternates lockfile\"));\n-\t\tgoto out;\n-\t}\n-\n-\tin = fopen(path, \"r\");\n-\tif (in) {\n-\t\tstruct strbuf line = STRBUF_INIT;\n-\n-\t\twhile (strbuf_getline(&line, in) != EOF) {\n-\t\t\tif (!strcmp(alternate, line.buf)) {\n-\t\t\t\tfound = 1;\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t\tfprintf_or_die(out, \"%s\\n\", line.buf);\n-\t\t}\n-\n-\t\tstrbuf_release(&line);\n-\t\tfclose(in);\n-\t} else if (errno != ENOENT) {\n-\t\tret = error_errno(_(\"unable to read alternates file\"));\n-\t\tgoto out;\n-\t}\n-\n-\tif (found) {\n-\t\trollback_lock_file(&lock);\n-\t} else {\n-\t\tfprintf_or_die(out, \"%s\\n\", alternate);\n-\t\tif (commit_lock_file(&lock)) {\n-\t\t\tret = error_errno(_(\"unable to move new alternates file into place\"));\n-\t\t\tgoto out;\n-\t\t}\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\tfree(path);\n-\treturn ret;\n-}\n-\n static int too_many_loose_objects(struct odb_source_files *files, int limit)\n {\n \tunsigned long loose_count;\n@@ -842,7 +789,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tfiles->base.write_object_stream = odb_source_files_write_object_stream;\n \tfiles->base.begin_transaction = odb_source_files_begin_transaction;\n \tfiles->base.read_alternates = odb_source_files_read_alternates;\n-\tfiles->base.write_alternate = odb_source_files_write_alternate;\n \tfiles->base.optimize = odb_source_files_optimize;\n \tfiles->base.optimize_required = odb_source_files_optimize_required;\n \ndiff --git a/odb/source-inmemory.c b/odb/source-inmemory.c\nindex 795672adf2..b00248dfb2 100644\n--- a/odb/source-inmemory.c\n+++ b/odb/source-inmemory.c\n@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t       const char *alternate UNUSED)\n-{\n-\treturn error(\"in-memory source does not support alternates\");\n-}\n-\n static void odb_source_inmemory_close(struct odb_source *source UNUSED)\n {\n }\n@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)\n \tsource->base.freshen_object = odb_source_inmemory_freshen_object;\n \tsource->base.begin_transaction = odb_source_inmemory_begin_transaction;\n \tsource->base.read_alternates = odb_source_inmemory_read_alternates;\n-\tsource->base.write_alternate = odb_source_inmemory_write_alternate;\n \n \treturn source;\n }\ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex bb3455dfbd..0f9b30bac1 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t    const char *alternate UNUSED)\n-{\n-\treturn error(\"loose source does not support alternates\");\n-}\n-\n static void odb_source_loose_clear_cache(struct odb_source_loose *loose)\n {\n \toidtree_clear(loose->cache);\n@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n \tloose->base.write_object_stream = odb_source_loose_write_object_stream;\n \tloose->base.begin_transaction = odb_source_loose_begin_transaction;\n \tloose->base.read_alternates = odb_source_loose_read_alternates;\n-\tloose->base.write_alternate = odb_source_loose_write_alternate;\n \n \tif (!is_absolute_path(loose->base.path))\n \t\tchdir_notify_register(NULL, odb_source_loose_reparent, loose);\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 630d955585..c2d253759c 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t     const char *alternate UNUSED)\n-{\n-\treturn error(\"packed backend cannot write alternates\");\n-}\n-\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n \tpacked->base.write_object_stream = odb_source_packed_write_object_stream;\n \tpacked->base.begin_transaction = odb_source_packed_begin_transaction;\n \tpacked->base.read_alternates = odb_source_packed_read_alternates;\n-\tpacked->base.write_alternate = odb_source_packed_write_alternate;\n \n \tif (!is_absolute_path(path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/odb/source.h b/odb/source.h\nindex 63f1c0c531..693a9fc604 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -286,19 +286,6 @@ struct odb_source {\n \tint (*read_alternates)(struct odb_source *source,\n \t\t\t       struct strvec *out);\n \n-\t/*\n-\t * This callback is expected to persist the singular alternate passed\n-\t * to it into its list of alternates. Any pre-existing alternates are\n-\t * expected to remain active. Subsequent calls to `read_alternates` are\n-\t * thus expected to yield the pre-existing list of alternates plus the\n-\t * newly added alternate appended to its end.\n-\t *\n-\t * The callback is expected to return 0 on success, a negative error\n-\t * code otherwise.\n-\t */\n-\tint (*write_alternate)(struct odb_source *source,\n-\t\t\t       const char *alternate);\n-\n \t/*\n \t * This callback is expected to optimize the object database source.\n \t * Returns 0 on success, a negative error code otherwise.\n@@ -518,19 +505,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,\n \treturn source->read_alternates(source, out);\n }\n \n-/*\n- * Write and persist a new alternate object database source for the given\n- * source. Any preexisting alternates are expected to stay valid, and the new\n- * alternate shall be appended to the end of the list.\n- *\n- * Returns 0 on success, a negative error code otherwise.\n- */\n-static inline int odb_source_write_alternate(struct odb_source *source,\n-\t\t\t\t\t      const char *alternate)\n-{\n-\treturn source->write_alternate(source, alternate);\n-}\n-\n /*\n  * Create a new transaction that can be used to write objects into a temporary\n  * staging area. The objects will only be persisted when the transaction is\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552345","messageId":"aqGlryiYlgfDtCHr@denethor","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im","subject":"Re: [PATCH v4 0/9] odb: write alternates at creation time","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-09-09T18:30:49Z","receivedAt":"2026-09-09T18:31:01Z","isPatch":true,"body":"On 26/09/09 07:48AM, Patrick Steinhardt wrote:\n> Changes in v4:\n>   - Add documentation for the different functions that play a role in\n>     creating repositories.\n\nThanks. This version looks good to me.\n\n-Justin\n"},{"id":"552426","messageId":"CAOLa=ZQy1G9ZH0abH1uJZ0t1H5B7b3ym_s_gUAs03FiAend9Lw@mail.gmail.com","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-1-d8a78ffc32e4@pks.im","subject":"Re: [PATCH v4 1/9] setup: split up concerns of `init_db()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-09-10T09:24:14Z","receivedAt":"2026-09-10T09:24:21Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The function `init_db()` is responsible for creating the on-disk\n> directory structure required for a Git repository. It is used by both\n> git-init(1) and git-clone(1), and because their expected behaviour is\n> different we support a couple of flags:\n>\n>   - The `QUIET` flag controls whether the command is quiet or not. For\n>     git-init(1) this is user-controllable, whereas for git-clone(1)\n>     we're always quiet.\n>\n>   - The `EXIST_OK` flag controls whether a preexisting repository is\n>     okay or not. For git-init(1) it is, for git-clone(1) it's not.\n>\n>   - The `SKIP_REFDB` flag controls whether the reference database should\n>     already be created or not. For git-init(1) we do, but for\n>     git-clone(1) we don't because it does not yet know about the default\n>     branch and about the remote object hash.\n>\n> Furthermore, we're about to add another divergence in behaviour, where\n> we have to also skip creation of the object database in git-clone(1).\n> This is becoming quite cumbersome though.\n>\n> Instead of introducing another flag, start to split up concerns of the\n> function so that we never create the reference or object database. This\n> becomes the responsibility of the caller, which is thus free to defer\n> their creation to a later point in time. This lets us get rid of most of\n> the divergent behaviour:\n>\n>   - We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags\n>     anymore.\n>\n>   - We don't need the `QUIET` flag anymore, as nothing prints output\n>     except for the final status message that tells the user that the\n>     repository has been (re)initialized. But as this message is specific\n>     to git-init(1), we can easily move it there.\n>\n> The only piece of information we still have to convey is whether or not\n> reinitialization of a preexisting repository is okay. This is handled by\n> a new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay\n> to reinitialize the repository. Furthermore, the pointer will be written\n> to to indicate whether the repository was reinitialized or not, which we\n> need in git-init(1) to print the correct initialization message.\n>\n> With these refactorings, `init_db()` is named quite misleadingly though,\n> as we don't create any of the reference or object databases anymore.\n> Rename it to `create_repository()`.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/clone.c   |  9 +++++----\n>  builtin/init-db.c | 32 ++++++++++++++++++++++++--------\n>  setup.c           | 54 +++++++++++++++++-------------------------------------\n>  setup.h           | 45 +++++++++++++++++++++++++++++++++------------\n>  4 files changed, 79 insertions(+), 61 deletions(-)\n>\n\n[snip]\n\n> @@ -2877,8 +2877,10 @@ int init_db(struct repository *repo,\n>\n>  \tsafe_create_dir(repo, git_dir, 0);\n>\n> -\treinit = create_default_files(repo, template_dir, original_git_dir,\n> -\t\t\t\t      &repo_fmt, init_shared_repository);\n> +\tif (!reinit_ok)\n> +\t\treinit_ok = &reinit_ignored;\n> +\t*reinit_ok = create_default_files(repo, template_dir, original_git_dir,\n> +\t\t\t\t\t  &repo_fmt, init_shared_repository);\n>\n>  \tif (repo_settings_get_shared_repository(repo)) {\n>  \t\tchar buf[10];\n> @@ -2901,29 +2903,7 @@ int init_db(struct repository *repo,\n>  \t\trepo_config_set(repo, \"receive.denyNonFastforwards\", \"true\");\n>  \t}\n>\n> -\tif (!(flags & INIT_DB_SKIP_REFDB))\n> -\t\tcreate_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);\n> -\tcreate_object_database(repo);\n> -\n> -\tstartup_info->have_repository = 1;\n> -\n> -\tif (!(flags & INIT_DB_QUIET)) {\n> -\t\tint len = strlen(git_dir);\n> -\n> -\t\tif (reinit)\n> -\t\t\tprintf(repo_settings_get_shared_repository(repo)\n> -\t\t\t       ? _(\"Reinitialized existing shared Git repository in %s%s\\n\")\n> -\t\t\t       : _(\"Reinitialized existing Git repository in %s%s\\n\"),\n> -\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n> -\t\telse\n> -\t\t\tprintf(repo_settings_get_shared_repository(repo)\n> -\t\t\t       ? _(\"Initialized empty shared Git repository in %s%s\\n\")\n> -\t\t\t       : _(\"Initialized empty Git repository in %s%s\\n\"),\n> -\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n> -\t}\n> -\n\nI was wondering if the order of initialization changes with the move,\nbut it stays the same. So all the changes look good.\n\n[snip]\n"},{"id":"552427","messageId":"CAOLa=ZSH5WtAej++SuWhupLjYv_rLQs8cCcXgETNjLH7_mkZyA@mail.gmail.com","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-2-d8a78ffc32e4@pks.im","subject":"Re: [PATCH v4 2/9] builtin/clone: defer setup of the object database","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-09-10T09:28:20Z","receivedAt":"2026-09-10T09:28:23Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> When cloning a repository we defer initialization of the reference\n> database. This is because we don't yet know all details required for us\n> to initialize the refdb in the first place. Most importantly, what we\n> are missing is information about the object hash.\n>\n> We don't do the same thing for the object database yet, but here we\n> essentially have the same problem. While the \"files\" database does not\n> need any information about the object format at creation time, alternate\n> backends are likely to require that information so that they can\n> properly set up their data structures.\n>\n> Besides this forward-looking future proofing though, we also have a\n> second use case for deferring initialization of the object database,\n> namely alternates. When initializing the object database we do not yet\n> know whether we'll need alternates or not because this depends on the\n> repository we're about to clone from. If it is a local repository and\n> the user has passed \"--refernce{,-if-able}\", then we will end up writing\n> alternates into the object database.\n>\n\nNit: s/refernce/reference\n\n> The ugly part though is that we cannot determine where the repository is\n> getting cloned from before it has been initialized. While we of course\n> already have access to the user-provided URI, that URI can be very well\n> rewritten via \"url.<base>.insteadOf\". We can of course read the global-\n> and system-level configuration to resolve it. But we explicitly resolve\n> the URI a second time after we have initialized the repository because\n> it can happen that we copy a \".git/config\" over from our templates, and\n> that file may cause us to rewrite the path.\n>\n> In a subsequent commit though we'll start to write alternates as part of\n> the repository initialization, so we'll need to have the URI properly\n> resolved before we can initialize the object database. This is ugly, but\n> as mentioned above it makes sense for us to defer its initialization\n> anyway so that we also know about the object hash already.\n>\n> Defer creation of the object database until after we have resolved the\n> URI.\n>\n> Note that this also requires us to defer the call to `setup_reference()`\n> until after we have created the object database. While you might think\n> that this function has something to do with references (\"refs/*\"), it is\n> in fact responsible for setting up alternates. Consequently, we can only\n> call it after we have created the object database already.\n>\n\nHaha. I like this last para, you kinda explained the thought I was\ngetting as I was getting it :)\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/clone.c | 8 ++++----\n>  1 file changed, 4 insertions(+), 4 deletions(-)\n>\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index 904d2d859f..bdcbd7aa1b 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -1188,7 +1188,6 @@ int cmd_clone(int argc,\n>  \tcreate_repository(the_repository, git_dir, real_git_dir, work_tree,\n>  \t\t\t  option_template, GIT_HASH_UNKNOWN, ref_storage_format,\n>  \t\t\t  do_not_override_repo_unix_permissions, NULL);\n> -\tcreate_object_database(the_repository);\n>\n>  \tif (real_git_dir) {\n>  \t\tfree((char *)git_dir);\n> @@ -1311,9 +1310,6 @@ int cmd_clone(int argc,\n>  \t\tstrbuf_reset(&key);\n>  \t}\n>\n> -\tif (option_required_reference.nr || option_optional_reference.nr)\n> -\t\tsetup_reference();\n> -\n\nSo this is the alternate setup, which we move down to after the\n`create_object_database()`. Looks good.\n\n>  \tremote = remote_get_early(remote_name);\n>\n>  \tif (!option_rev)\n> @@ -1342,6 +1338,10 @@ int cmd_clone(int argc,\n>  \tif (option_local > 0 && !is_local)\n>  \t\twarning(_(\"--local is ignored\"));\n>\n> +\tcreate_object_database(the_repository);\n> +\tif (option_required_reference.nr || option_optional_reference.nr)\n> +\t\tsetup_reference();\n> +\n>  \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n>  \ttransport_set_verbosity(transport, option_verbosity, option_progress);\n>  \ttransport->family = family;\n>\n> --\n> 2.55.0.1074.ge7621b4bad.dirty\n"},{"id":"552428","messageId":"CAOLa=ZSrDLpuAghLzXt2Fyzimi2n4qkEtVCcRLDZcqx5QNkTZg@mail.gmail.com","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-3-d8a78ffc32e4@pks.im","subject":"Re: [PATCH v4 3/9] builtin/clone: move around `setup_reference()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-09-10T09:29:30Z","receivedAt":"2026-09-10T09:29:33Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> In a subsequent commit, `setup_reference()` will start to call\n> `copy_alternates()`. Prepare for this by moving the function further\n> down so that we can avoid adding a declaration.\n>\n\nWill we rename setup_reference() now that we agreed it is a bit\nambiguous, let's see..\n\n[snip]\n"},{"id":"552429","messageId":"CAOLa=ZQf3XcX_G1H+vNSQtpU-Oj56pda0WcJAORw-kQMEPvTTw@mail.gmail.com","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-4-d8a78ffc32e4@pks.im","subject":"Re: [PATCH v4 4/9] builtin/clone: refactor handling of \"--reference{,-if-able}\"","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-09-10T09:37:31Z","receivedAt":"2026-09-10T09:37:36Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Users can pass \"--reference{,-if-able}\" to git-clone(1) to instruct it\n> to set up alternates for the newly created repository. This allows it to\n> reuse objects from the source repository so that in the best case we\n> don't have to clone all objects over.\n>\n> Those options are handled by the confusingly named `setup_reference()`\n> function -- without the above context, one might rightfully believe that\n> it was about refs, not about alternates. The function itself is rather\n> simple: we loop through all provided alternate paths and then, if such\n> an alternate is valid, we write it to the object database.\n>\n> In subsequent commits we're about to consolidate the complete setup of\n> alternates into this function, and furthermore we'll refactor the setup\n> of the object database to handle doing this for us instead of writing\n> the alternates into it one by one.\n>\n> Prepare for this refactoring by collecting the alternates into a strvec.\n> Rename the function to `collect_alternates()` to clarify its scope.\n>\n\nYay!\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------\n>  1 file changed, 28 insertions(+), 16 deletions(-)\n>\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index ac5843d7b9..8786a49332 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)\n>  \treturn canon;\n>  }\n>\n> -static int add_one_reference(struct string_list_item *item, void *cb_data)\n> +struct add_one_alternate_data {\n\nSo this is `add_one_alternate()`'s data, was a bit confusing cause I\nfirst read that this was information regarding a single alternate, but\nthen it also has a field called `alternates`.\n\n> +\tstruct strvec *alternates;\n> +\tint required;\n\nQuestion: here and other places, I see some of the boolean-ish fields\nbeing declared as `int`. It doesn't matter, but is there a reason?\n\n> +};\n> +\n> +static int add_one_alternate(struct string_list_item *item, void *cb_data)\n>  {\n> +\tstruct add_one_alternate_data *data = cb_data;\n>  \tstruct strbuf err = STRBUF_INIT;\n> -\tint *required = cb_data;\n>  \tchar *ref_git = compute_alternate_path(item->string, &err);\n>\n>  \tif (!ref_git) {\n> -\t\tif (*required)\n> +\t\tif (data->required)\n>  \t\t\tdie(\"%s\", err.buf);\n>  \t\telse\n>  \t\t\tfprintf(stderr,\n>  \t\t\t\t_(\"info: Could not add alternate for '%s': %s\\n\"),\n>  \t\t\t\titem->string, err.buf);\n>  \t} else {\n> -\t\tstruct strbuf sb = STRBUF_INIT;\n> -\t\tstrbuf_addf(&sb, \"%s/objects\", ref_git);\n> -\t\todb_add_to_alternates_file(the_repository->objects, sb.buf);\n> -\t\tstrbuf_release(&sb);\n> +\t\tstrvec_pushf(data->alternates, \"%s/objects\", ref_git);\n>  \t}\n>\n>  \tstrbuf_release(&err);\n> @@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n>  \tfclose(in);\n>  }\n>\n> -static void setup_reference(void)\n> +static void collect_alternates(struct strvec *alternates)\n>  {\n> -\tint required = 1;\n> -\tfor_each_string_list(&option_required_reference,\n> -\t\t\t     add_one_reference, &required);\n> -\trequired = 0;\n> -\tfor_each_string_list(&option_optional_reference,\n> -\t\t\t     add_one_reference, &required);\n> +\tif (option_required_reference.nr || option_optional_reference.nr) {\n> +\t\tstruct add_one_alternate_data data = {\n> +\t\t\t.alternates = alternates,\n> +\t\t\t.required = 1,\n> +\t\t};\n> +\n> +\t\tfor_each_string_list(&option_required_reference,\n> +\t\t\t\t     add_one_alternate, &data);\n> +\t\tdata.required = 0;\n> +\t\tfor_each_string_list(&option_optional_reference,\n> +\t\t\t\t     add_one_alternate, &data);\n> +\t}\n\nNit: might be nicer to read\n\n     if (!option_required_reference.nr && !option_optional_reference.nr)\n        return;\n\nThe rest of it doesn't have to be in the `if` block.\n\n>  }\n>\n>  static void mkdir_if_missing(const char *pathname, mode_t mode)\n> @@ -999,6 +1007,7 @@ int cmd_clone(int argc,\n>  \t\t\t   N_(\"uri\"), N_(\"a URI for downloading bundles before fetching from origin remote\")),\n>  \t\tOPT_END()\n>  \t};\n> +\tstruct strvec alternates = STRVEC_INIT;\n>\n>  \tconst char * const builtin_clone_usage[] = {\n>  \t\tN_(\"git clone [<options>] [--] <repo> [<dir>]\"),\n> @@ -1339,8 +1348,10 @@ int cmd_clone(int argc,\n>  \t\twarning(_(\"--local is ignored\"));\n>\n>  \tcreate_object_database(the_repository);\n> -\tif (option_required_reference.nr || option_optional_reference.nr)\n> -\t\tsetup_reference();\n> +\tcollect_alternates(&alternates);\n> +\n> +\tfor (size_t i = 0; i < alternates.nr; i++)\n> +\t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n>\n\nSo now adding the alternates is moved out of `collect_alternates()`.\nNit: might be nice to mention this in the commit message.\n\n>  \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n>  \ttransport_set_verbosity(transport, option_verbosity, option_progress);\n> @@ -1638,6 +1649,7 @@ int cmd_clone(int argc,\n>  \tstring_list_clear(&option_not, 0);\n>  \tstring_list_clear(&option_config, 0);\n>  \tstring_list_clear(&server_options, 0);\n> +\tstrvec_clear(&alternates);\n>\n>  \tfree(remote_name);\n>  \tstrbuf_release(&reflog_msg);\n>\n> --\n> 2.55.0.1074.ge7621b4bad.dirty\n"},{"id":"552431","messageId":"CAOLa=ZRu6D16En7hSmT19geC6OvptRXQg98ZAaDgoh4X5Yc6qw@mail.gmail.com","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-5-d8a78ffc32e4@pks.im","subject":"Re: [PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-09-10T10:52:38Z","receivedAt":"2026-09-10T10:52:40Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> When cloning a local repository with \"--shared\" we add that repository\n> to the new repository's alternates. This is done in `clone_local()`,\n> which is responsible for performing local clones.\n>\n> Move the logic into `collect_alternates()` to unify our setup of\n> alternates. Furthermore, this will allow us to set up alternates right\n> at creation time of the object database.\n>\n> Note that the logic for cloning a local repository with \"--no-shared\" is\n> not yet part of `collect_alternates()`. This will be handled in the next\n> commit.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/clone.c | 23 ++++++++++++++---------\n>  1 file changed, 14 insertions(+), 9 deletions(-)\n>\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index 8786a49332..011fc867c8 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n>  \tfclose(in);\n>  }\n>\n> -static void collect_alternates(struct strvec *alternates)\n> +static void collect_alternates(struct strvec *alternates,\n> +\t\t\t       const char *src_repo, bool is_local)\n>  {\n>  \tif (option_required_reference.nr || option_optional_reference.nr) {\n>  \t\tstruct add_one_alternate_data data = {\n> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)\n>  \t\tfor_each_string_list(&option_optional_reference,\n>  \t\t\t\t     add_one_alternate, &data);\n>  \t}\n> +\n> +\tif (is_local) {\n>\n\nShouldn't we also check for `option_shared` here?\n\n> +\t\tstruct strbuf commondir = STRBUF_INIT;\n> +\n> +\t\tget_common_dir(&commondir, src_repo);\n> +\t\tif (option_shared)\n> +\t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n> +\n> +\t\tstrbuf_release(&commondir);\n> +\t}\n>  }\n>\n>  static void mkdir_if_missing(const char *pathname, mode_t mode)\n> @@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,\n>\n>  static void clone_local(const char *src_repo, const char *dest_repo)\n>  {\n> -\tif (option_shared) {\n> -\t\tstruct strbuf alt = STRBUF_INIT;\n> -\t\tget_common_dir(&alt, src_repo);\n> -\t\tstrbuf_addstr(&alt, \"/objects\");\n> -\t\todb_add_to_alternates_file(the_repository->objects, alt.buf);\n> -\t\tstrbuf_release(&alt);\n> -\t} else {\n> +\tif (!option_shared) {\n>  \t\tstruct strbuf src = STRBUF_INIT;\n>  \t\tstruct strbuf dest = STRBUF_INIT;\n>  \t\tget_common_dir(&src, src_repo);\n> @@ -1348,7 +1353,7 @@ int cmd_clone(int argc,\n>  \t\twarning(_(\"--local is ignored\"));\n>\n>  \tcreate_object_database(the_repository);\n> -\tcollect_alternates(&alternates);\n> +\tcollect_alternates(&alternates, path, is_local);\n>\n>  \tfor (size_t i = 0; i < alternates.nr; i++)\n>  \t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n>\n> --\n> 2.55.0.1074.ge7621b4bad.dirty\n"},{"id":"552432","messageId":"CAOLa=ZQLdB23c0b0tzUeYew=Gkqc11fLnROy0RUCUBg6qxxy4w@mail.gmail.com","threadId":"66216","inReplyTo":"CAOLa=ZRu6D16En7hSmT19geC6OvptRXQg98ZAaDgoh4X5Yc6qw@mail.gmail.com","subject":"Re: [PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-09-10T10:54:50Z","receivedAt":"2026-09-10T10:54:55Z","isPatch":true,"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> Patrick Steinhardt <ps@pks.im> writes:\n>\n>> When cloning a local repository with \"--shared\" we add that repository\n>> to the new repository's alternates. This is done in `clone_local()`,\n>> which is responsible for performing local clones.\n>>\n>> Move the logic into `collect_alternates()` to unify our setup of\n>> alternates. Furthermore, this will allow us to set up alternates right\n>> at creation time of the object database.\n>>\n>> Note that the logic for cloning a local repository with \"--no-shared\" is\n>> not yet part of `collect_alternates()`. This will be handled in the next\n>> commit.\n>>\n>> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n>> ---\n>>  builtin/clone.c | 23 ++++++++++++++---------\n>>  1 file changed, 14 insertions(+), 9 deletions(-)\n>>\n>> diff --git a/builtin/clone.c b/builtin/clone.c\n>> index 8786a49332..011fc867c8 100644\n>> --- a/builtin/clone.c\n>> +++ b/builtin/clone.c\n>> @@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n>>  \tfclose(in);\n>>  }\n>>\n>> -static void collect_alternates(struct strvec *alternates)\n>> +static void collect_alternates(struct strvec *alternates,\n>> +\t\t\t       const char *src_repo, bool is_local)\n>>  {\n>>  \tif (option_required_reference.nr || option_optional_reference.nr) {\n>>  \t\tstruct add_one_alternate_data data = {\n>> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)\n>>  \t\tfor_each_string_list(&option_optional_reference,\n>>  \t\t\t\t     add_one_alternate, &data);\n>>  \t}\n>> +\n>> +\tif (is_local) {\n>>\n>\n> Shouldn't we also check for `option_shared` here?\n>\n>> +\t\tstruct strbuf commondir = STRBUF_INIT;\n>> +\n>> +\t\tget_common_dir(&commondir, src_repo);\n>> +\t\tif (option_shared)\n>> +\t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n>> +\n\nSo we do it here, but then commondir is initiated but unused otherwise.\n\n>> +\t\tstrbuf_release(&commondir);\n>> +\t}\n>>  }\n>>\n\n[snip]\n"},{"id":"552433","messageId":"CAOLa=ZSNC-w3aC_SjhhbemWryvSr7ySZGKTXs8BkYr7AmdKXOA@mail.gmail.com","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-6-d8a78ffc32e4@pks.im","subject":"Re: [PATCH v4 6/9] builtin/clone: move setup of alternates for non-shared local clones","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-09-10T11:00:46Z","receivedAt":"2026-09-10T11:00:50Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Similar as in the preceding commit, move the setup of alternates for\n> local clones with \"--no-shared\" into `collect_alternates()`. With this\n> step, the complete setup of alternates is now handled by that function.\n>\n> Note that besides moving stuff around, it also fixes a bug: previously,\n> we did not know to resolve the referenced repository's common directory.\n> Consequently, when referencing a worktree we failed to resolve\n> alternates. But as `collect_alternates()` already knows to resolve the\n> commondir for \"--local\" we can simply reuse this resolved path for our\n> purpose.\n>\n> Add two tests, the first one of which exercises this bug to avoid future\n> regressions. The second test ensures that we properly handle relative\n> alternates for a referenced worktree.\n\n[snip]\n\n>  static void collect_alternates(struct strvec *alternates,\n> @@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,\n>  \t\tget_common_dir(&commondir, src_repo);\n>  \t\tif (option_shared)\n>  \t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n> +\t\telse\n> +\t\t\tread_alternates(alternates, commondir.buf);\n>\n\nOkay so this is why we did what we did in the prev patch. Makes sense.\n\n>  \t\tstrbuf_release(&commondir);\n>  \t}\n[snip]\n"},{"id":"552434","messageId":"CAOLa=ZQaPstiQmXm9=TyWPUxL6X2=Lcqeg6y2XeXzSJDpq-GBA@mail.gmail.com","threadId":"66216","inReplyTo":"20260909-pks-odb-write-alternates-at-creation-time-v4-7-d8a78ffc32e4@pks.im","subject":"Re: [PATCH v4 7/9] odb/source: support writing alternates when creating the database","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-09-10T11:10:31Z","receivedAt":"2026-09-10T11:10:33Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Add the ability to write alternates when creating the object database.\n> This change allows us to remove the `write_alternates()` callback in a\n> subsequent patch.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--\n>  odb/source.h       | 17 +++++++++---\n>  setup.c            |  4 ++-\n>  3 files changed, 91 insertions(+), 6 deletions(-)\n>\n> diff --git a/odb/source-files.c b/odb/source-files.c\n> index b7b3a297bb..8fe65d91f8 100644\n> --- a/odb/source-files.c\n> +++ b/odb/source-files.c\n> @@ -18,6 +18,7 @@\n>  #include \"run-command.h\"\n>  #include \"strbuf.h\"\n>  #include \"string-list.h\"\n> +#include \"strmap.h\"\n>  #include \"strvec.h\"\n>  #include \"tree.h\"\n>  #include \"write-or-die.h\"\n> @@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)\n>  \todb_source_close(&files->packed->base);\n>  }\n>\n> -static int odb_source_files_create_on_disk(struct odb_source *source)\n> +static int odb_source_files_create_on_disk(struct odb_source *source,\n> +\t\t\t\t\t   const struct odb_create_on_disk_options *opts)\n>  {\n> +\tstruct lock_file alternates_lock = LOCK_INIT;\n>  \tstruct strbuf path = STRBUF_INIT;\n> +\tstruct strset seen = STRSET_INIT;\n> +\tstruct strbuf line = STRBUF_INIT;\n> +\tint ret;\n>\n>  \tsafe_create_dir(source->odb->repo, source->path, 1);\n>\n> @@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)\n>  \tstrbuf_addf(&path, \"%s/info\", source->path);\n>  \tsafe_create_dir(source->odb->repo, path.buf, 1);\n>\n> +\tif (opts->alternates && opts->alternates->nr) {\n> +\t\tFILE *alternates, *orig;\n> +\n\nSo this is similar to what we already do in\n`odb_source_files_write_alternate()`.\n\n> +\t\tstrbuf_reset(&path);\n> +\t\tstrbuf_addf(&path, \"%s/info/alternates\", source->path);\n> +\n> +\t\trepo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,\n> +\t\t\t\t\t       path.buf, LOCK_DIE_ON_ERROR);\n> +\n> +\t\talternates = fdopen_lock_file(&alternates_lock, \"w\");\n> +\t\tif (!alternates) {\n> +\t\t\tret = error_errno(_(\"unable to fdopen alternates lockfile\"));\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n> +\t\t/*\n> +\t\t * The alternates file may already exist, e.g. when it has been\n> +\t\t * seeded from a template directory. Read any preexisting\n> +\t\t * entries so that we don't end up writing duplicates.\n> +\t\t */\n> +\t\torig = fopen(path.buf, \"r\");\n> +\t\tif (orig) {\n> +\t\t\twhile (strbuf_getline(&line, orig) != EOF) {\n> +\t\t\t\tstrset_add(&seen, line.buf);\n> +\t\t\t\tfprintf(alternates, \"%s\\n\", line.buf);\n> +\t\t\t}\n> +\n> +\t\t\tif (ferror(orig)) {\n> +\t\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n> +\t\t\t\tfclose(orig);\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n\nShouldn't this be checked inside the for loop with every `fprintf` call?\n\n> +\n> +\t\t\tfclose(orig);\n> +\t\t} else if (errno != ENOENT) {\n> +\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n> +\t\tfor (size_t i = 0; i < opts->alternates->nr; i++) {\n> +\t\t\tconst char *alternate = opts->alternates->v[i];\n> +\t\t\tif (!strset_add(&seen, alternate))\n> +\t\t\t\tcontinue;\n> +\t\t\tfprintf(alternates, \"%s\\n\", alternate);\n> +\t\t}\n> +\n> +\t\tif (ferror(alternates)) {\n> +\t\t\tret = error_errno(_(\"unable to write alternates file\"));\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n\nsame here.\n\n> +\t\tif (commit_lock_file(&alternates_lock)) {\n> +\t\t\tret = error_errno(_(\"unable to commit alternates file\"));\n> +\t\t\tgoto out;\n> +\t\t}\n> +\t}\n> +\n> +\t/* Reprepare the object database to activate alternates. */\n> +\todb_reprepare(source->odb);\n> +\n> +\tret = 0;\n> +\n> +out:\n> +\trollback_lock_file(&alternates_lock);\n> +\tstrbuf_release(&line);\n>  \tstrbuf_release(&path);\n> -\treturn 0;\n> +\tstrset_clear(&seen);\n> +\treturn ret;\n>  }\n>\n>  static void odb_source_files_prepare(struct odb_source *source,\n> diff --git a/odb/source.h b/odb/source.h\n> index ea8675247e..63f1c0c531 100644\n> --- a/odb/source.h\n> +++ b/odb/source.h\n> @@ -36,6 +36,15 @@ struct object_id;\n>  struct odb_stream;\n>  struct strvec;\n>\n> +struct odb_create_on_disk_options {\n> +\t/*\n> +\t * Alternates that shall be written into the newly created object\n> +\t * database. Whether or not this option can be handled is specific to\n> +\t * the backend.\n> +\t */\n\nWould it make sense to formalize errors thrown by backends, so we know\nwhen a backend specifically cannot handle alternates?\n\n> +\tconst struct strvec *alternates;\n> +};\n> +\n\n[snip]\n"},{"id":"552444","messageId":"aqK-IYVKCKp9ExmY@pks.im","threadId":"66216","inReplyTo":"CAOLa=ZQf3XcX_G1H+vNSQtpU-Oj56pda0WcJAORw-kQMEPvTTw@mail.gmail.com","subject":"Re: [PATCH v4 4/9] builtin/clone: refactor handling of \"--reference{,-if-able}\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-10T14:26:41Z","receivedAt":"2026-09-10T14:26:54Z","isPatch":true,"body":"On Thu, Sep 10, 2026 at 02:37:31AM -0700, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/builtin/clone.c b/builtin/clone.c\n> > index ac5843d7b9..8786a49332 100644\n> > --- a/builtin/clone.c\n> > +++ b/builtin/clone.c\n> > @@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)\n> >  \treturn canon;\n> >  }\n> >\n> > -static int add_one_reference(struct string_list_item *item, void *cb_data)\n> > +struct add_one_alternate_data {\n> \n> So this is `add_one_alternate()`'s data, was a bit confusing cause I\n> first read that this was information regarding a single alternate, but\n> then it also has a field called `alternates`.\n\nWe can also rename this to `collect_one_alternate()` and the structure\n`collect_alternates_data`.\n\n> > +\tstruct strvec *alternates;\n> > +\tint required;\n> \n> Question: here and other places, I see some of the boolean-ish fields\n> being declared as `int`. It doesn't matter, but is there a reason?\n\nNo, there isn't. I'm probably just still getting used to new fancy\nfeatures like that.\n\n> > @@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n> >  \tfclose(in);\n> >  }\n> >\n> > -static void setup_reference(void)\n> > +static void collect_alternates(struct strvec *alternates)\n> >  {\n> > -\tint required = 1;\n> > -\tfor_each_string_list(&option_required_reference,\n> > -\t\t\t     add_one_reference, &required);\n> > -\trequired = 0;\n> > -\tfor_each_string_list(&option_optional_reference,\n> > -\t\t\t     add_one_reference, &required);\n> > +\tif (option_required_reference.nr || option_optional_reference.nr) {\n> > +\t\tstruct add_one_alternate_data data = {\n> > +\t\t\t.alternates = alternates,\n> > +\t\t\t.required = 1,\n> > +\t\t};\n> > +\n> > +\t\tfor_each_string_list(&option_required_reference,\n> > +\t\t\t\t     add_one_alternate, &data);\n> > +\t\tdata.required = 0;\n> > +\t\tfor_each_string_list(&option_optional_reference,\n> > +\t\t\t\t     add_one_alternate, &data);\n> > +\t}\n> \n> Nit: might be nicer to read\n> \n>      if (!option_required_reference.nr && !option_optional_reference.nr)\n>         return;\n> \n> The rest of it doesn't have to be in the `if` block.\n\nWe'll extend this over the next couple patches, so I'll leave this\nas-is.\n\n> > @@ -1339,8 +1348,10 @@ int cmd_clone(int argc,\n> >  \t\twarning(_(\"--local is ignored\"));\n> >\n> >  \tcreate_object_database(the_repository);\n> > -\tif (option_required_reference.nr || option_optional_reference.nr)\n> > -\t\tsetup_reference();\n> > +\tcollect_alternates(&alternates);\n> > +\n> > +\tfor (size_t i = 0; i < alternates.nr; i++)\n> > +\t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n> >\n> \n> So now adding the alternates is moved out of `collect_alternates()`.\n> Nit: might be nice to mention this in the commit message.\n\nWill do.\n\nPatrick\n"},{"id":"552445","messageId":"aqK-Lh6HEU1BCbBf@pks.im","threadId":"66216","inReplyTo":"CAOLa=ZQLdB23c0b0tzUeYew=Gkqc11fLnROy0RUCUBg6qxxy4w@mail.gmail.com","subject":"Re: [PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-10T14:26:54Z","receivedAt":"2026-09-10T14:27:00Z","isPatch":true,"body":"On Thu, Sep 10, 2026 at 05:54:50AM -0500, Karthik Nayak wrote:\n> Karthik Nayak <karthik.188@gmail.com> writes:\n> > Patrick Steinhardt <ps@pks.im> writes:\n> >> diff --git a/builtin/clone.c b/builtin/clone.c\n> >> index 8786a49332..011fc867c8 100644\n> >> --- a/builtin/clone.c\n> >> +++ b/builtin/clone.c\n> >> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)\n> >>  \t\tfor_each_string_list(&option_optional_reference,\n> >>  \t\t\t\t     add_one_alternate, &data);\n> >>  \t}\n> >> +\n> >> +\tif (is_local) {\n> >>\n> >\n> > Shouldn't we also check for `option_shared` here?\n> >\n> >> +\t\tstruct strbuf commondir = STRBUF_INIT;\n> >> +\n> >> +\t\tget_common_dir(&commondir, src_repo);\n> >> +\t\tif (option_shared)\n> >> +\t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n> >> +\n> \n> So we do it here, but then commondir is initiated but unused otherwise.\n\nIt is, yes. But this is done to prep for the next step, where the\n`commondir` variable will be used regardless of whether or not we use\n`option_shared`.\n\nI'll adapt the commit message a bit.\n\nPatrick\n"},{"id":"552449","messageId":"20260910-pks-odb-write-alternates-at-creation-time-v5-0-8d10c4238edc@pks.im","threadId":"66216","inReplyTo":"20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im","subject":"[PATCH v5 0/9] odb: write alternates at creation time","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-10T15:09:10Z","receivedAt":"2026-09-10T15:09:21Z","isPatch":true,"body":"Hi,\n\nwriting alternates into the object database currently happens via\n`odb_source_write_alternate()`. But while that creates the ability to\ncreate alternates at arbitrary points of a source's lifetime, we don't\nuse that functionality in the first place. Instead, we only ever write\nalternates when creating a new repository.\n\nThis design is suboptimal due to a couple of reasons:\n\n  - It requires us to have a `write_alternates()` callback, which is\n    overblown as we never even write alternates to an object database\n    after it has been created.\n\n  - We're about to make alternates an implementation detail of the\n    object database's backend in a future patch series, so alternate\n    implementations may not even support them.\n\n  - The backend has more flexibility with how exactly alternates are\n    configured when it itself is in full control over their setup at the\n    time where it creates the object database itself.\n\nThis patch series thus refactors how we handle alternates so that we\ndon't write them ad-hoc anymore. Instead, the series introduces a new\noption for `odb_source_create_on_disk()` that makes it handle those\nalternates at creation time.\n\nThis is part of the bigger goal of moving handling of alternates into\nthe \"files\" backend.\n\nThis series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)\nwith ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop\n`alternates_db` field, 2026-08-17) merged into it.\n\nChanges in v5:\n  - Rename `add_one_alternate` and `add_one_alternate_data` to\n    `collect_one_alternate` and `collect_alternates_data` to clarify\n    their intent a bit.\n  - Drop extra parameter in `collect_alternates()`.\n  - Clarify why we compute `commondir` even though it's unused.\n  - Link to v4: https://patch.msgid.link/20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im\n\nChanges in v4:\n  - Add documentation for the different functions that play a role in\n    creating repositories.\n  - Link to v3: https://patch.msgid.link/20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im\n\nChanges in v3:\n  - Refactor `init_db()` to not create the reference and object database\n    at all anymore. Instead, it's now called `create_repository()` and\n    it is responsible for creating the initial repository skeleton,\n    only. This allows us to get rid of the flags and overall makes the\n    logic more straight-forward by moving the command-specific logic\n    into the respective commands.\n  - A couple of typo fixes.\n  - Link to v2: https://patch.msgid.link/20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im\n\nChanges in v2:\n  - Use a lockfile to write \"info/alternates\" during creation time.\n  - Remove useless \"strvec.h\" include by reordering declarations a bit.\n  - Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (9):\n      setup: split up concerns of `init_db()`\n      builtin/clone: defer setup of the object database\n      builtin/clone: move around `setup_reference()`\n      builtin/clone: refactor handling of \"--reference{,-if-able}\"\n      builtin/clone: move setup of alternates for shared local clones\n      builtin/clone: move setup of alternates for non-shared local clones\n      odb/source: support writing alternates when creating the database\n      builtin/clone: write alternates via `odb_create_on_disk()`\n      odb/source: remove the ability to write alternates\n\n builtin/clone.c            | 111 +++++++++++++++++++++++---------------\n builtin/init-db.c          |  32 ++++++++---\n odb.c                      |   9 ----\n odb.h                      |   7 ---\n odb/source-files.c         | 130 ++++++++++++++++++++++++++-------------------\n odb/source-inmemory.c      |   7 ---\n odb/source-loose.c         |   7 ---\n odb/source-packed.c        |   7 ---\n odb/source.h               |  43 +++++----------\n setup.c                    |  61 ++++++++-------------\n setup.h                    |  47 +++++++++++-----\n t/t5604-clone-reference.sh |  25 +++++++++\n 12 files changed, 264 insertions(+), 222 deletions(-)\n\nRange-diff versus v4:\n\n 1:  30a1faa807 =  1:  e5d789fdfa setup: split up concerns of `init_db()`\n 2:  7a646ebe92 !  2:  94a19390b4 builtin/clone: defer setup of the object database\n    @@ Commit message\n         namely alternates. When initializing the object database we do not yet\n         know whether we'll need alternates or not because this depends on the\n         repository we're about to clone from. If it is a local repository and\n    -    the user has passed \"--refernce{,-if-able}\", then we will end up writing\n    -    alternates into the object database.\n    +    the user has passed \"--reference{,-if-able}\", then we will end up\n    +    writing alternates into the object database.\n     \n         The ugly part though is that we cannot determine where the repository is\n         getting cloned from before it has been initialized. While we of course\n 3:  af77d0e9e3 =  3:  e9ccb5b1bd builtin/clone: move around `setup_reference()`\n 4:  2ca937bce1 !  4:  30c4933fb0 builtin/clone: refactor handling of \"--reference{,-if-able}\"\n    @@ Commit message\n         the alternates into it one by one.\n     \n         Prepare for this refactoring by collecting the alternates into a strvec.\n    -    Rename the function to `collect_alternates()` to clarify its scope.\n    +    Rename the function to `collect_alternates()` to clarify its scope, as\n    +    it does not set up the references itself anymore.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n    @@ builtin/clone.c: static char *get_repo_path(const char *repo, int *is_bundle)\n      }\n      \n     -static int add_one_reference(struct string_list_item *item, void *cb_data)\n    -+struct add_one_alternate_data {\n    ++struct collect_alternates_data {\n     +\tstruct strvec *alternates;\n    -+\tint required;\n    ++\tbool required;\n     +};\n     +\n    -+static int add_one_alternate(struct string_list_item *item, void *cb_data)\n    ++static int collect_one_alternate(struct string_list_item *item, void *cb_data)\n      {\n    -+\tstruct add_one_alternate_data *data = cb_data;\n    ++\tstruct collect_alternates_data *data = cb_data;\n      \tstruct strbuf err = STRBUF_INIT;\n     -\tint *required = cb_data;\n      \tchar *ref_git = compute_alternate_path(item->string, &err);\n    @@ builtin/clone.c: static void copy_alternates(struct strbuf *src, const char *src\n     -\tfor_each_string_list(&option_optional_reference,\n     -\t\t\t     add_one_reference, &required);\n     +\tif (option_required_reference.nr || option_optional_reference.nr) {\n    -+\t\tstruct add_one_alternate_data data = {\n    ++\t\tstruct collect_alternates_data data = {\n     +\t\t\t.alternates = alternates,\n    -+\t\t\t.required = 1,\n    ++\t\t\t.required = true,\n     +\t\t};\n     +\n     +\t\tfor_each_string_list(&option_required_reference,\n    -+\t\t\t\t     add_one_alternate, &data);\n    -+\t\tdata.required = 0;\n    ++\t\t\t\t     collect_one_alternate, &data);\n    ++\t\tdata.required = false;\n     +\t\tfor_each_string_list(&option_optional_reference,\n    -+\t\t\t\t     add_one_alternate, &data);\n    ++\t\t\t\t     collect_one_alternate, &data);\n     +\t}\n      }\n      \n 5:  4ba60fc24b !  5:  5141c179e2 builtin/clone: move setup of alternates for shared local clones\n    @@ Commit message\n     \n         Note that the logic for cloning a local repository with \"--no-shared\" is\n         not yet part of `collect_alternates()`. This will be handled in the next\n    -    commit.\n    +    commit, but means that at this step, we may compute `commondir` without\n    +    it being used. It will become used in the next step though.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n    @@ builtin/clone.c: static void copy_alternates(struct strbuf *src, const char *src\n      \n     -static void collect_alternates(struct strvec *alternates)\n     +static void collect_alternates(struct strvec *alternates,\n    -+\t\t\t       const char *src_repo, bool is_local)\n    ++\t\t\t       const char *local_source_repo)\n      {\n      \tif (option_required_reference.nr || option_optional_reference.nr) {\n    - \t\tstruct add_one_alternate_data data = {\n    + \t\tstruct collect_alternates_data data = {\n     @@ builtin/clone.c: static void collect_alternates(struct strvec *alternates)\n      \t\tfor_each_string_list(&option_optional_reference,\n    - \t\t\t\t     add_one_alternate, &data);\n    + \t\t\t\t     collect_one_alternate, &data);\n      \t}\n     +\n    -+\tif (is_local) {\n    ++\tif (local_source_repo) {\n     +\t\tstruct strbuf commondir = STRBUF_INIT;\n     +\n    -+\t\tget_common_dir(&commondir, src_repo);\n    ++\t\tget_common_dir(&commondir, local_source_repo);\n     +\t\tif (option_shared)\n     +\t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n     +\n    @@ builtin/clone.c: int cmd_clone(int argc,\n      \n      \tcreate_object_database(the_repository);\n     -\tcollect_alternates(&alternates);\n    -+\tcollect_alternates(&alternates, path, is_local);\n    ++\tcollect_alternates(&alternates, is_local ? path : NULL);\n      \n      \tfor (size_t i = 0; i < alternates.nr; i++)\n      \t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n 6:  4f3c8fb936 !  6:  b0ed987d1c builtin/clone: move setup of alternates for non-shared local clones\n    @@ Commit message\n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## builtin/clone.c ##\n    -@@ builtin/clone.c: static int add_one_alternate(struct string_list_item *item, void *cb_data)\n    +@@ builtin/clone.c: static int collect_one_alternate(struct string_list_item *item, void *cb_data)\n      \treturn 0;\n      }\n      \n    @@ builtin/clone.c: static void copy_alternates(struct strbuf *src, const char *src\n      \n      static void collect_alternates(struct strvec *alternates,\n     @@ builtin/clone.c: static void collect_alternates(struct strvec *alternates,\n    - \t\tget_common_dir(&commondir, src_repo);\n    + \t\tget_common_dir(&commondir, local_source_repo);\n      \t\tif (option_shared)\n      \t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n     +\t\telse\n 7:  47548ac464 =  7:  4cc70ca1dd odb/source: support writing alternates when creating the database\n 8:  4e3be623dd !  8:  2cea0312bd builtin/clone: write alternates via `odb_create_on_disk()`\n    @@ builtin/clone.c: int cmd_clone(int argc,\n      \t\twarning(_(\"--local is ignored\"));\n      \n     -\tcreate_object_database(the_repository);\n    - \tcollect_alternates(&alternates, path, is_local);\n    + \tcollect_alternates(&alternates, is_local ? path : NULL);\n     -\n     -\tfor (size_t i = 0; i < alternates.nr; i++)\n     -\t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n 9:  7207b7e2ed =  9:  e1f8f728a6 odb/source: remove the ability to write alternates\n\n---\nbase-commit: afa255aeb620346d56a2c01fb5ae9163513c56d7\nchange-id: 20260813-pks-odb-write-alternates-at-creation-time-64010deb94a0\n\n"},{"id":"552451","messageId":"20260910-pks-odb-write-alternates-at-creation-time-v5-1-8d10c4238edc@pks.im","threadId":"66216","inReplyTo":"20260910-pks-odb-write-alternates-at-creation-time-v5-0-8d10c4238edc@pks.im","subject":"[PATCH v5 1/9] setup: split up concerns of `init_db()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-10T15:09:11Z","receivedAt":"2026-09-10T15:09:24Z","isPatch":true,"body":"The function `init_db()` is responsible for creating the on-disk\ndirectory structure required for a Git repository. It is used by both\ngit-init(1) and git-clone(1), and because their expected behaviour is\ndifferent we support a couple of flags:\n\n  - The `QUIET` flag controls whether the command is quiet or not. For\n    git-init(1) this is user-controllable, whereas for git-clone(1)\n    we're always quiet.\n\n  - The `EXIST_OK` flag controls whether a preexisting repository is\n    okay or not. For git-init(1) it is, for git-clone(1) it's not.\n\n  - The `SKIP_REFDB` flag controls whether the reference database should\n    already be created or not. For git-init(1) we do, but for\n    git-clone(1) we don't because it does not yet know about the default\n    branch and about the remote object hash.\n\nFurthermore, we're about to add another divergence in behaviour, where\nwe have to also skip creation of the object database in git-clone(1).\nThis is becoming quite cumbersome though.\n\nInstead of introducing another flag, start to split up concerns of the\nfunction so that we never create the reference or object database. This\nbecomes the responsibility of the caller, which is thus free to defer\ntheir creation to a later point in time. This lets us get rid of most of\nthe divergent behaviour:\n\n  - We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags\n    anymore.\n\n  - We don't need the `QUIET` flag anymore, as nothing prints output\n    except for the final status message that tells the user that the\n    repository has been (re)initialized. But as this message is specific\n    to git-init(1), we can easily move it there.\n\nThe only piece of information we still have to convey is whether or not\nreinitialization of a preexisting repository is okay. This is handled by\na new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay\nto reinitialize the repository. Furthermore, the pointer will be written\nto to indicate whether the repository was reinitialized or not, which we\nneed in git-init(1) to print the correct initialization message.\n\nWith these refactorings, `init_db()` is named quite misleadingly though,\nas we don't create any of the reference or object databases anymore.\nRename it to `create_repository()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c   |  9 +++++----\n builtin/init-db.c | 32 ++++++++++++++++++++++++--------\n setup.c           | 54 +++++++++++++++++-------------------------------------\n setup.h           | 45 +++++++++++++++++++++++++++++++++------------\n 4 files changed, 79 insertions(+), 61 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 5b25cca510..904d2d859f 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1185,10 +1185,10 @@ int cmd_clone(int argc,\n \t * repository, and reference backends may persist that information into\n \t * their on-disk data structures.\n \t */\n-\tinit_db(the_repository, git_dir, real_git_dir, work_tree, option_template,\n-\t\tGIT_HASH_UNKNOWN, ref_storage_format, NULL,\n-\t\tdo_not_override_repo_unix_permissions,\n-\t\tINIT_DB_QUIET | INIT_DB_SKIP_REFDB);\n+\tcreate_repository(the_repository, git_dir, real_git_dir, work_tree,\n+\t\t\t  option_template, GIT_HASH_UNKNOWN, ref_storage_format,\n+\t\t\t  do_not_override_repo_unix_permissions, NULL);\n+\tcreate_object_database(the_repository);\n \n \tif (real_git_dir) {\n \t\tfree((char *)git_dir);\n@@ -1445,6 +1445,7 @@ int cmd_clone(int argc,\n \tinitialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);\n \trepo_set_hash_algo(the_repository, hash_algo);\n \tcreate_reference_database(the_repository, NULL, 1);\n+\tstartup_info->have_repository = 1;\n \n \t/*\n \t * Before fetching from the remote, download and install bundle\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex e96b1283b7..f2c7e3be6d 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -80,7 +80,7 @@ int cmd_init_db(int argc,\n \tchar *work_tree = NULL;\n \tconst char *template_dir = NULL;\n \tchar *template_dir_to_free = NULL;\n-\tunsigned int flags = 0;\n+\tint quiet = 0;\n \tint bare = startup_info->force_bare_repository ? 1 : -1;\n \tconst char *object_format = NULL;\n \tconst char *ref_format = NULL;\n@@ -102,7 +102,7 @@ int cmd_init_db(int argc,\n \t\t\t.flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG,\n \t\t\t.callback = shared_callback\n \t\t},\n-\t\tOPT_BIT('q', \"quiet\", &flags, N_(\"be quiet\"), INIT_DB_QUIET),\n+\t\tOPT_BOOL('q', \"quiet\", &quiet, N_(\"be quiet\")),\n \t\tOPT_STRING(0, \"separate-git-dir\", &real_git_dir, N_(\"gitdir\"),\n \t\t\t   N_(\"separate git dir from working tree\")),\n \t\tOPT_STRING('b', \"initial-branch\", &initial_branch, N_(\"name\"),\n@@ -113,7 +113,7 @@ int cmd_init_db(int argc,\n \t\t\t   N_(\"specify the reference format to use\")),\n \t\tOPT_END()\n \t};\n-\tint ret;\n+\tint reinit;\n \n \targc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);\n \n@@ -247,14 +247,30 @@ int cmd_init_db(int argc,\n \t\tdie(_(\"--separate-git-dir incompatible with bare repository\"));\n \t}\n \n-\tflags |= INIT_DB_EXIST_OK;\n-\tret = init_db(the_repository, git_dir, real_git_dir, work_tree,\n-\t\t      template_dir, hash_algo, ref_storage_format, initial_branch,\n-\t\t      init_shared_repository, flags);\n+\tcreate_repository(the_repository, git_dir, real_git_dir, work_tree,\n+\t\t\t  template_dir, hash_algo, ref_storage_format,\n+\t\t\t  init_shared_repository, &reinit);\n+\tcreate_reference_database(the_repository, initial_branch, quiet);\n+\tcreate_object_database(the_repository);\n+\n+\tif (!quiet) {\n+\t\tint len = strlen(git_dir);\n+\n+\t\tif (reinit)\n+\t\t\tprintf(repo_settings_get_shared_repository(the_repository)\n+\t\t\t       ? _(\"Reinitialized existing shared Git repository in %s%s\\n\")\n+\t\t\t       : _(\"Reinitialized existing Git repository in %s%s\\n\"),\n+\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n+\t\telse\n+\t\t\tprintf(repo_settings_get_shared_repository(the_repository)\n+\t\t\t       ? _(\"Initialized empty shared Git repository in %s%s\\n\")\n+\t\t\t       : _(\"Initialized empty Git repository in %s%s\\n\"),\n+\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n+\t}\n \n \tfree(template_dir_to_free);\n \tfree(real_git_dir_to_free);\n \tfree(work_tree);\n \tfree(git_dir);\n-\treturn ret;\n+\treturn 0;\n }\ndiff --git a/setup.c b/setup.c\nindex d90654f584..8c7b97f92e 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,\n \treturn reinit;\n }\n \n-static void create_object_database(struct repository *repo)\n+void create_object_database(struct repository *repo)\n {\n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\n@@ -2822,17 +2822,17 @@ static void repository_format_configure(struct repository_format *repo_fmt,\n \t}\n }\n \n-int init_db(struct repository *repo,\n-\t    const char *git_dir,\n-\t    const char *real_git_dir,\n-\t    const char *worktree,\n-\t    const char *template_dir, int hash,\n-\t    enum ref_storage_format ref_storage_format,\n-\t    const char *initial_branch,\n-\t    int init_shared_repository, unsigned int flags)\n+void create_repository(struct repository *repo,\n+\t\t       const char *git_dir,\n+\t\t       const char *real_git_dir,\n+\t\t       const char *worktree,\n+\t\t       const char *template_dir,\n+\t\t       int hash,\n+\t\t       enum ref_storage_format ref_storage_format,\n+\t\t       int init_shared_repository,\n+\t\t       int *reinit_ok)\n {\n-\tint reinit;\n-\tint exist_ok = flags & INIT_DB_EXIST_OK;\n+\tint reinit_ignored;\n \tchar *original_git_dir = real_pathdup(git_dir, 1);\n \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2840,10 +2840,10 @@ int init_db(struct repository *repo,\n \tif (real_git_dir) {\n \t\tstruct stat st;\n \n-\t\tif (!exist_ok && !stat(git_dir, &st))\n+\t\tif (!reinit_ok && !stat(git_dir, &st))\n \t\t\tdie(_(\"%s already exists\"), git_dir);\n \n-\t\tif (!exist_ok && !stat(real_git_dir, &st))\n+\t\tif (!reinit_ok && !stat(real_git_dir, &st))\n \t\t\tdie(_(\"%s already exists\"), real_git_dir);\n \n \t\tapply_and_export_relative_gitdir(repo, real_git_dir, 1);\n@@ -2877,8 +2877,10 @@ int init_db(struct repository *repo,\n \n \tsafe_create_dir(repo, git_dir, 0);\n \n-\treinit = create_default_files(repo, template_dir, original_git_dir,\n-\t\t\t\t      &repo_fmt, init_shared_repository);\n+\tif (!reinit_ok)\n+\t\treinit_ok = &reinit_ignored;\n+\t*reinit_ok = create_default_files(repo, template_dir, original_git_dir,\n+\t\t\t\t\t  &repo_fmt, init_shared_repository);\n \n \tif (repo_settings_get_shared_repository(repo)) {\n \t\tchar buf[10];\n@@ -2901,29 +2903,7 @@ int init_db(struct repository *repo,\n \t\trepo_config_set(repo, \"receive.denyNonFastforwards\", \"true\");\n \t}\n \n-\tif (!(flags & INIT_DB_SKIP_REFDB))\n-\t\tcreate_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);\n-\tcreate_object_database(repo);\n-\n-\tstartup_info->have_repository = 1;\n-\n-\tif (!(flags & INIT_DB_QUIET)) {\n-\t\tint len = strlen(git_dir);\n-\n-\t\tif (reinit)\n-\t\t\tprintf(repo_settings_get_shared_repository(repo)\n-\t\t\t       ? _(\"Reinitialized existing shared Git repository in %s%s\\n\")\n-\t\t\t       : _(\"Reinitialized existing Git repository in %s%s\\n\"),\n-\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n-\t\telse\n-\t\t\tprintf(repo_settings_get_shared_repository(repo)\n-\t\t\t       ? _(\"Initialized empty shared Git repository in %s%s\\n\")\n-\t\t\t       : _(\"Initialized empty Git repository in %s%s\\n\"),\n-\t\t\t       git_dir, len && git_dir[len-1] != '/' ? \"/\" : \"\");\n-\t}\n-\n \tclear_repository_format(&repo_fmt);\n \tstrbuf_release(&err);\n \tfree(original_git_dir);\n-\treturn 0;\n }\ndiff --git a/setup.h b/setup.h\nindex 763fd384e8..f1c1ed65fb 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -256,24 +256,45 @@ int apply_repository_format(struct repository *repo,\n \n const char *get_template_dir(const char *option_template);\n \n-#define INIT_DB_QUIET      (1 << 0)\n-#define INIT_DB_EXIST_OK   (1 << 1)\n-#define INIT_DB_SKIP_REFDB (1 << 2)\n-\n-int init_db(struct repository *repo,\n-\t    const char *git_dir,\n-\t    const char *real_git_dir,\n-\t    const char *worktree,\n-\t    const char *template_dir, int hash_algo,\n-\t    enum ref_storage_format ref_storage_format,\n-\t    const char *initial_branch, int init_shared_repository,\n-\t    unsigned int flags);\n+/*\n+ * Create the repository by creating the necessary directory structures,\n+ * setting up the configuration and configuring the repository's format. If\n+ * `template_dir` is set, copy over templates from that directory. Furthermore,\n+ * if and only if `reinit_ok` is a non-NULL pointer, then the function may\n+ * reinitialize a preexisting repository. In that case, the pointer will be set\n+ * to `1` in case the repo was reinitialized and `0` if it didn't exist yet.\n+ *\n+ * Note that this function does not create the reference and object databases.\n+ */\n+void create_repository(struct repository *repo,\n+\t\t       const char *git_dir,\n+\t\t       const char *real_git_dir,\n+\t\t       const char *worktree,\n+\t\t       const char *template_dir,\n+\t\t       int hash_algo,\n+\t\t       enum ref_storage_format ref_storage_format,\n+\t\t       int init_shared_repository,\n+\t\t       int *reinit_ok);\n+\n void initialize_repository_version(struct repository *repo,\n \t\t\t\t   int hash_algo,\n \t\t\t\t   enum ref_storage_format ref_storage_format,\n \t\t\t\t   int reinit);\n+\n+/*\n+ * Create the reference database for the repository. The repository and its ref\n+ * storage format must have already been configured properly before calling\n+ * this function. When set, `initial_branch` overrides the default branch that\n+ * HEAD will point to.\n+ */\n void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);\n \n+/*\n+ * Create the object database for the repository. The repository must have\n+ * already been configured properly before calling this function.\n+ */\n+void create_object_database(struct repository *repo);\n+\n /*\n  * NOTE NOTE NOTE!!\n  *\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552450","messageId":"20260910-pks-odb-write-alternates-at-creation-time-v5-2-8d10c4238edc@pks.im","threadId":"66216","inReplyTo":"20260910-pks-odb-write-alternates-at-creation-time-v5-0-8d10c4238edc@pks.im","subject":"[PATCH v5 2/9] builtin/clone: defer setup of the object database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-10T15:09:12Z","receivedAt":"2026-09-10T15:09:25Z","isPatch":true,"body":"When cloning a repository we defer initialization of the reference\ndatabase. This is because we don't yet know all details required for us\nto initialize the refdb in the first place. Most importantly, what we\nare missing is information about the object hash.\n\nWe don't do the same thing for the object database yet, but here we\nessentially have the same problem. While the \"files\" database does not\nneed any information about the object format at creation time, alternate\nbackends are likely to require that information so that they can\nproperly set up their data structures.\n\nBesides this forward-looking future proofing though, we also have a\nsecond use case for deferring initialization of the object database,\nnamely alternates. When initializing the object database we do not yet\nknow whether we'll need alternates or not because this depends on the\nrepository we're about to clone from. If it is a local repository and\nthe user has passed \"--reference{,-if-able}\", then we will end up\nwriting alternates into the object database.\n\nThe ugly part though is that we cannot determine where the repository is\ngetting cloned from before it has been initialized. While we of course\nalready have access to the user-provided URI, that URI can be very well\nrewritten via \"url.<base>.insteadOf\". We can of course read the global-\nand system-level configuration to resolve it. But we explicitly resolve\nthe URI a second time after we have initialized the repository because\nit can happen that we copy a \".git/config\" over from our templates, and\nthat file may cause us to rewrite the path.\n\nIn a subsequent commit though we'll start to write alternates as part of\nthe repository initialization, so we'll need to have the URI properly\nresolved before we can initialize the object database. This is ugly, but\nas mentioned above it makes sense for us to defer its initialization\nanyway so that we also know about the object hash already.\n\nDefer creation of the object database until after we have resolved the\nURI.\n\nNote that this also requires us to defer the call to `setup_reference()`\nuntil after we have created the object database. While you might think\nthat this function has something to do with references (\"refs/*\"), it is\nin fact responsible for setting up alternates. Consequently, we can only\ncall it after we have created the object database already.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 904d2d859f..bdcbd7aa1b 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1188,7 +1188,6 @@ int cmd_clone(int argc,\n \tcreate_repository(the_repository, git_dir, real_git_dir, work_tree,\n \t\t\t  option_template, GIT_HASH_UNKNOWN, ref_storage_format,\n \t\t\t  do_not_override_repo_unix_permissions, NULL);\n-\tcreate_object_database(the_repository);\n \n \tif (real_git_dir) {\n \t\tfree((char *)git_dir);\n@@ -1311,9 +1310,6 @@ int cmd_clone(int argc,\n \t\tstrbuf_reset(&key);\n \t}\n \n-\tif (option_required_reference.nr || option_optional_reference.nr)\n-\t\tsetup_reference();\n-\n \tremote = remote_get_early(remote_name);\n \n \tif (!option_rev)\n@@ -1342,6 +1338,10 @@ int cmd_clone(int argc,\n \tif (option_local > 0 && !is_local)\n \t\twarning(_(\"--local is ignored\"));\n \n+\tcreate_object_database(the_repository);\n+\tif (option_required_reference.nr || option_optional_reference.nr)\n+\t\tsetup_reference();\n+\n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\n \ttransport->family = family;\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552452","messageId":"20260910-pks-odb-write-alternates-at-creation-time-v5-3-8d10c4238edc@pks.im","threadId":"66216","inReplyTo":"20260910-pks-odb-write-alternates-at-creation-time-v5-0-8d10c4238edc@pks.im","subject":"[PATCH v5 3/9] builtin/clone: move around `setup_reference()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-10T15:09:13Z","receivedAt":"2026-09-10T15:09:28Z","isPatch":true,"body":"In a subsequent commit, `setup_reference()` will start to call\n`copy_alternates()`. Prepare for this by moving the function further\ndown so that we can avoid adding a declaration.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex bdcbd7aa1b..ac5843d7b9 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)\n \treturn 0;\n }\n \n-static void setup_reference(void)\n-{\n-\tint required = 1;\n-\tfor_each_string_list(&option_required_reference,\n-\t\t\t     add_one_reference, &required);\n-\trequired = 0;\n-\tfor_each_string_list(&option_optional_reference,\n-\t\t\t     add_one_reference, &required);\n-}\n-\n static void copy_alternates(struct strbuf *src, const char *src_repo)\n {\n \t/*\n@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n+static void setup_reference(void)\n+{\n+\tint required = 1;\n+\tfor_each_string_list(&option_required_reference,\n+\t\t\t     add_one_reference, &required);\n+\trequired = 0;\n+\tfor_each_string_list(&option_optional_reference,\n+\t\t\t     add_one_reference, &required);\n+}\n+\n static void mkdir_if_missing(const char *pathname, mode_t mode)\n {\n \tstruct stat st;\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552453","messageId":"20260910-pks-odb-write-alternates-at-creation-time-v5-4-8d10c4238edc@pks.im","threadId":"66216","inReplyTo":"20260910-pks-odb-write-alternates-at-creation-time-v5-0-8d10c4238edc@pks.im","subject":"[PATCH v5 4/9] builtin/clone: refactor handling of \"--reference{,-if-able}\"","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-10T15:09:14Z","receivedAt":"2026-09-10T15:09:30Z","isPatch":true,"body":"Users can pass \"--reference{,-if-able}\" to git-clone(1) to instruct it\nto set up alternates for the newly created repository. This allows it to\nreuse objects from the source repository so that in the best case we\ndon't have to clone all objects over.\n\nThose options are handled by the confusingly named `setup_reference()`\nfunction -- without the above context, one might rightfully believe that\nit was about refs, not about alternates. The function itself is rather\nsimple: we loop through all provided alternate paths and then, if such\nan alternate is valid, we write it to the object database.\n\nIn subsequent commits we're about to consolidate the complete setup of\nalternates into this function, and furthermore we'll refactor the setup\nof the object database to handle doing this for us instead of writing\nthe alternates into it one by one.\n\nPrepare for this refactoring by collecting the alternates into a strvec.\nRename the function to `collect_alternates()` to clarify its scope, as\nit does not set up the references itself anymore.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------\n 1 file changed, 28 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex ac5843d7b9..08d913d306 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)\n \treturn canon;\n }\n \n-static int add_one_reference(struct string_list_item *item, void *cb_data)\n+struct collect_alternates_data {\n+\tstruct strvec *alternates;\n+\tbool required;\n+};\n+\n+static int collect_one_alternate(struct string_list_item *item, void *cb_data)\n {\n+\tstruct collect_alternates_data *data = cb_data;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint *required = cb_data;\n \tchar *ref_git = compute_alternate_path(item->string, &err);\n \n \tif (!ref_git) {\n-\t\tif (*required)\n+\t\tif (data->required)\n \t\t\tdie(\"%s\", err.buf);\n \t\telse\n \t\t\tfprintf(stderr,\n \t\t\t\t_(\"info: Could not add alternate for '%s': %s\\n\"),\n \t\t\t\titem->string, err.buf);\n \t} else {\n-\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\tstrbuf_addf(&sb, \"%s/objects\", ref_git);\n-\t\todb_add_to_alternates_file(the_repository->objects, sb.buf);\n-\t\tstrbuf_release(&sb);\n+\t\tstrvec_pushf(data->alternates, \"%s/objects\", ref_git);\n \t}\n \n \tstrbuf_release(&err);\n@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n-static void setup_reference(void)\n+static void collect_alternates(struct strvec *alternates)\n {\n-\tint required = 1;\n-\tfor_each_string_list(&option_required_reference,\n-\t\t\t     add_one_reference, &required);\n-\trequired = 0;\n-\tfor_each_string_list(&option_optional_reference,\n-\t\t\t     add_one_reference, &required);\n+\tif (option_required_reference.nr || option_optional_reference.nr) {\n+\t\tstruct collect_alternates_data data = {\n+\t\t\t.alternates = alternates,\n+\t\t\t.required = true,\n+\t\t};\n+\n+\t\tfor_each_string_list(&option_required_reference,\n+\t\t\t\t     collect_one_alternate, &data);\n+\t\tdata.required = false;\n+\t\tfor_each_string_list(&option_optional_reference,\n+\t\t\t\t     collect_one_alternate, &data);\n+\t}\n }\n \n static void mkdir_if_missing(const char *pathname, mode_t mode)\n@@ -999,6 +1007,7 @@ int cmd_clone(int argc,\n \t\t\t   N_(\"uri\"), N_(\"a URI for downloading bundles before fetching from origin remote\")),\n \t\tOPT_END()\n \t};\n+\tstruct strvec alternates = STRVEC_INIT;\n \n \tconst char * const builtin_clone_usage[] = {\n \t\tN_(\"git clone [<options>] [--] <repo> [<dir>]\"),\n@@ -1339,8 +1348,10 @@ int cmd_clone(int argc,\n \t\twarning(_(\"--local is ignored\"));\n \n \tcreate_object_database(the_repository);\n-\tif (option_required_reference.nr || option_optional_reference.nr)\n-\t\tsetup_reference();\n+\tcollect_alternates(&alternates);\n+\n+\tfor (size_t i = 0; i < alternates.nr; i++)\n+\t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n \n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\n@@ -1638,6 +1649,7 @@ int cmd_clone(int argc,\n \tstring_list_clear(&option_not, 0);\n \tstring_list_clear(&option_config, 0);\n \tstring_list_clear(&server_options, 0);\n+\tstrvec_clear(&alternates);\n \n \tfree(remote_name);\n \tstrbuf_release(&reflog_msg);\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552454","messageId":"20260910-pks-odb-write-alternates-at-creation-time-v5-5-8d10c4238edc@pks.im","threadId":"66216","inReplyTo":"20260910-pks-odb-write-alternates-at-creation-time-v5-0-8d10c4238edc@pks.im","subject":"[PATCH v5 5/9] builtin/clone: move setup of alternates for shared local clones","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-10T15:09:15Z","receivedAt":"2026-09-10T15:09:32Z","isPatch":true,"body":"When cloning a local repository with \"--shared\" we add that repository\nto the new repository's alternates. This is done in `clone_local()`,\nwhich is responsible for performing local clones.\n\nMove the logic into `collect_alternates()` to unify our setup of\nalternates. Furthermore, this will allow us to set up alternates right\nat creation time of the object database.\n\nNote that the logic for cloning a local repository with \"--no-shared\" is\nnot yet part of `collect_alternates()`. This will be handled in the next\ncommit, but means that at this step, we may compute `commondir` without\nit being used. It will become used in the next step though.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c | 23 ++++++++++++++---------\n 1 file changed, 14 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 08d913d306..d397fd36b2 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \tfclose(in);\n }\n \n-static void collect_alternates(struct strvec *alternates)\n+static void collect_alternates(struct strvec *alternates,\n+\t\t\t       const char *local_source_repo)\n {\n \tif (option_required_reference.nr || option_optional_reference.nr) {\n \t\tstruct collect_alternates_data data = {\n@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)\n \t\tfor_each_string_list(&option_optional_reference,\n \t\t\t\t     collect_one_alternate, &data);\n \t}\n+\n+\tif (local_source_repo) {\n+\t\tstruct strbuf commondir = STRBUF_INIT;\n+\n+\t\tget_common_dir(&commondir, local_source_repo);\n+\t\tif (option_shared)\n+\t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n+\n+\t\tstrbuf_release(&commondir);\n+\t}\n }\n \n static void mkdir_if_missing(const char *pathname, mode_t mode)\n@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,\n \n static void clone_local(const char *src_repo, const char *dest_repo)\n {\n-\tif (option_shared) {\n-\t\tstruct strbuf alt = STRBUF_INIT;\n-\t\tget_common_dir(&alt, src_repo);\n-\t\tstrbuf_addstr(&alt, \"/objects\");\n-\t\todb_add_to_alternates_file(the_repository->objects, alt.buf);\n-\t\tstrbuf_release(&alt);\n-\t} else {\n+\tif (!option_shared) {\n \t\tstruct strbuf src = STRBUF_INIT;\n \t\tstruct strbuf dest = STRBUF_INIT;\n \t\tget_common_dir(&src, src_repo);\n@@ -1348,7 +1353,7 @@ int cmd_clone(int argc,\n \t\twarning(_(\"--local is ignored\"));\n \n \tcreate_object_database(the_repository);\n-\tcollect_alternates(&alternates);\n+\tcollect_alternates(&alternates, is_local ? path : NULL);\n \n \tfor (size_t i = 0; i < alternates.nr; i++)\n \t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552455","messageId":"20260910-pks-odb-write-alternates-at-creation-time-v5-6-8d10c4238edc@pks.im","threadId":"66216","inReplyTo":"20260910-pks-odb-write-alternates-at-creation-time-v5-0-8d10c4238edc@pks.im","subject":"[PATCH v5 6/9] builtin/clone: move setup of alternates for non-shared local clones","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-10T15:09:16Z","receivedAt":"2026-09-10T15:09:34Z","isPatch":true,"body":"Similar as in the preceding commit, move the setup of alternates for\nlocal clones with \"--no-shared\" into `collect_alternates()`. With this\nstep, the complete setup of alternates is now handled by that function.\n\nNote that besides moving stuff around, it also fixes a bug: previously,\nwe did not know to resolve the referenced repository's common directory.\nConsequently, when referencing a worktree we failed to resolve\nalternates. But as `collect_alternates()` already knows to resolve the\ncommondir for \"--local\" we can simply reuse this resolved path for our\npurpose.\n\nAdd two tests, the first one of which exercises this bug to avoid future\nregressions. The second test ensures that we properly handle relative\nalternates for a referenced worktree.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c            | 34 +++++++++++++++++++++++-----------\n t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++\n 2 files changed, 48 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex d397fd36b2..17353a8e1f 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -181,7 +181,7 @@ static int collect_one_alternate(struct string_list_item *item, void *cb_data)\n \treturn 0;\n }\n \n-static void copy_alternates(struct strbuf *src, const char *src_repo)\n+static void read_alternates(struct strvec *alternates, const char *src_repo)\n {\n \t/*\n \t * Read from the source objects/info/alternates file\n@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)\n \t * to turn entries with paths relative to the original\n \t * absolute, so that they can be used in the new repository.\n \t */\n-\tFILE *in = xfopen(src->buf, \"r\");\n+\tFILE *in;\n+\tstruct strbuf path = STRBUF_INIT;\n \tstruct strbuf line = STRBUF_INIT;\n \n+\tstrbuf_addf(&path, \"%s/objects/info/alternates\", src_repo);\n+\n+\tin = fopen(path.buf, \"r\");\n+\tif (!in) {\n+\t\tif (errno == ENOENT)\n+\t\t\tgoto out;\n+\t\tdie_errno(\"could not read alternates file '%s'\", path.buf);\n+\t}\n+\n \twhile (strbuf_getline(&line, in) != EOF) {\n \t\tchar *abs_path;\n \t\tif (!line.len || line.buf[0] == '#')\n \t\t\tcontinue;\n \t\tif (is_absolute_path(line.buf)) {\n-\t\t\todb_add_to_alternates_file(the_repository->objects,\n-\t\t\t\t\t\t   line.buf);\n+\t\t\tstrvec_push(alternates, line.buf);\n \t\t\tcontinue;\n \t\t}\n \t\tabs_path = mkpathdup(\"%s/objects/%s\", src_repo, line.buf);\n \t\tif (!normalize_path_copy(abs_path, abs_path))\n-\t\t\todb_add_to_alternates_file(the_repository->objects,\n-\t\t\t\t\t\t   abs_path);\n+\t\t\tstrvec_push(alternates, abs_path);\n \t\telse\n \t\t\twarning(\"skipping invalid relative alternate: %s/%s\",\n \t\t\t\tsrc_repo, line.buf);\n \t\tfree(abs_path);\n \t}\n+\n+out:\n+\tstrbuf_release(&path);\n \tstrbuf_release(&line);\n-\tfclose(in);\n+\tif (in)\n+\t\tfclose(in);\n }\n \n static void collect_alternates(struct strvec *alternates,\n@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,\n \t\tget_common_dir(&commondir, local_source_repo);\n \t\tif (option_shared)\n \t\t\tstrvec_pushf(alternates, \"%s/objects\", commondir.buf);\n+\t\telse\n+\t\t\tread_alternates(alternates, commondir.buf);\n \n \t\tstrbuf_release(&commondir);\n \t}\n@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,\n \t\t\tcontinue;\n \t\t}\n \n-\t\t/* Files that cannot be copied bit-for-bit... */\n-\t\tif (!fspathcmp(iter->relative_path, \"info/alternates\")) {\n-\t\t\tcopy_alternates(src, src_repo);\n+\t\t/* Alternates were already handled earlier. */\n+\t\tif (!fspathcmp(iter->relative_path, \"info/alternates\"))\n \t\t\tcontinue;\n-\t\t}\n \n \t\tif (unlink(dest->buf) && errno != ENOENT)\n \t\t\tdie_errno(_(\"failed to unlink '%s'\"), dest->buf);\ndiff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh\nindex 39a0c318df..9e4b98fdb8 100755\n--- a/t/t5604-clone-reference.sh\n+++ b/t/t5604-clone-reference.sh\n@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '\n \tgit clone --no-local --reference graph.git --dissociate orig clone\n '\n \n+test_expect_success 'local clone from linked worktree carries over alternates' '\n+\trm -fr base derived derived-wt dst expect &&\n+\tgit init base &&\n+\ttest_commit -C base one &&\n+\tgit clone --shared base derived &&\n+\tgit -C derived worktree add ../derived-wt &&\n+\tgit clone derived-wt dst &&\n+\techo \"$(pwd)/base/.git/objects\" >expect &&\n+\ttest_cmp expect dst/.git/objects/info/alternates &&\n+\tgit -C dst fsck\n+'\n+\n+test_expect_success 'local clone from linked worktree resolves relative alternates' '\n+\trm -fr base derived derived-wt dst expect &&\n+\tgit init base &&\n+\ttest_commit -C base one &&\n+\tgit clone --shared base derived &&\n+\techo \"../../../base/.git/objects\" >derived/.git/objects/info/alternates &&\n+\tgit -C derived worktree add ../derived-wt &&\n+\tgit clone derived-wt dst &&\n+\techo \"$(pwd)/base/.git/objects\" >expect &&\n+\ttest_cmp expect dst/.git/objects/info/alternates &&\n+\tgit -C dst fsck\n+'\n+\n test_done\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552456","messageId":"20260910-pks-odb-write-alternates-at-creation-time-v5-7-8d10c4238edc@pks.im","threadId":"66216","inReplyTo":"20260910-pks-odb-write-alternates-at-creation-time-v5-0-8d10c4238edc@pks.im","subject":"[PATCH v5 7/9] odb/source: support writing alternates when creating the database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-10T15:09:17Z","receivedAt":"2026-09-10T15:09:37Z","isPatch":true,"body":"Add the ability to write alternates when creating the object database.\nThis change allows us to remove the `write_alternates()` callback in a\nsubsequent patch.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--\n odb/source.h       | 17 +++++++++---\n setup.c            |  4 ++-\n 3 files changed, 91 insertions(+), 6 deletions(-)\n\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex b7b3a297bb..8fe65d91f8 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -18,6 +18,7 @@\n #include \"run-command.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n+#include \"strmap.h\"\n #include \"strvec.h\"\n #include \"tree.h\"\n #include \"write-or-die.h\"\n@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)\n \todb_source_close(&files->packed->base);\n }\n \n-static int odb_source_files_create_on_disk(struct odb_source *source)\n+static int odb_source_files_create_on_disk(struct odb_source *source,\n+\t\t\t\t\t   const struct odb_create_on_disk_options *opts)\n {\n+\tstruct lock_file alternates_lock = LOCK_INIT;\n \tstruct strbuf path = STRBUF_INIT;\n+\tstruct strset seen = STRSET_INIT;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tint ret;\n \n \tsafe_create_dir(source->odb->repo, source->path, 1);\n \n@@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)\n \tstrbuf_addf(&path, \"%s/info\", source->path);\n \tsafe_create_dir(source->odb->repo, path.buf, 1);\n \n+\tif (opts->alternates && opts->alternates->nr) {\n+\t\tFILE *alternates, *orig;\n+\n+\t\tstrbuf_reset(&path);\n+\t\tstrbuf_addf(&path, \"%s/info/alternates\", source->path);\n+\n+\t\trepo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,\n+\t\t\t\t\t       path.buf, LOCK_DIE_ON_ERROR);\n+\n+\t\talternates = fdopen_lock_file(&alternates_lock, \"w\");\n+\t\tif (!alternates) {\n+\t\t\tret = error_errno(_(\"unable to fdopen alternates lockfile\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/*\n+\t\t * The alternates file may already exist, e.g. when it has been\n+\t\t * seeded from a template directory. Read any preexisting\n+\t\t * entries so that we don't end up writing duplicates.\n+\t\t */\n+\t\torig = fopen(path.buf, \"r\");\n+\t\tif (orig) {\n+\t\t\twhile (strbuf_getline(&line, orig) != EOF) {\n+\t\t\t\tstrset_add(&seen, line.buf);\n+\t\t\t\tfprintf(alternates, \"%s\\n\", line.buf);\n+\t\t\t}\n+\n+\t\t\tif (ferror(orig)) {\n+\t\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n+\t\t\t\tfclose(orig);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\tfclose(orig);\n+\t\t} else if (errno != ENOENT) {\n+\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (size_t i = 0; i < opts->alternates->nr; i++) {\n+\t\t\tconst char *alternate = opts->alternates->v[i];\n+\t\t\tif (!strset_add(&seen, alternate))\n+\t\t\t\tcontinue;\n+\t\t\tfprintf(alternates, \"%s\\n\", alternate);\n+\t\t}\n+\n+\t\tif (ferror(alternates)) {\n+\t\t\tret = error_errno(_(\"unable to write alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (commit_lock_file(&alternates_lock)) {\n+\t\t\tret = error_errno(_(\"unable to commit alternates file\"));\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+\t/* Reprepare the object database to activate alternates. */\n+\todb_reprepare(source->odb);\n+\n+\tret = 0;\n+\n+out:\n+\trollback_lock_file(&alternates_lock);\n+\tstrbuf_release(&line);\n \tstrbuf_release(&path);\n-\treturn 0;\n+\tstrset_clear(&seen);\n+\treturn ret;\n }\n \n static void odb_source_files_prepare(struct odb_source *source,\ndiff --git a/odb/source.h b/odb/source.h\nindex ea8675247e..63f1c0c531 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -36,6 +36,15 @@ struct object_id;\n struct odb_stream;\n struct strvec;\n \n+struct odb_create_on_disk_options {\n+\t/*\n+\t * Alternates that shall be written into the newly created object\n+\t * database. Whether or not this option can be handled is specific to\n+\t * the backend.\n+\t */\n+\tconst struct strvec *alternates;\n+};\n+\n /*\n  * The source is the part of the object database that stores the actual\n  * objects. It thus encapsulates the logic to read and write the specific\n@@ -106,7 +115,8 @@ struct odb_source {\n \t * This callback may be NULL in case the source does not need any\n \t * on-disk setup.\n \t */\n-\tint (*create_on_disk)(struct odb_source *source);\n+\tint (*create_on_disk)(struct odb_source *source,\n+\t\t\t      const struct odb_create_on_disk_options *opts);\n \n \t/*\n \t * This callback is expected to prepare the source so that it becomes\n@@ -356,11 +366,12 @@ static inline void odb_source_close(struct odb_source *source)\n  * Create on-disk data structures that are required for this source to operate\n  * correctly. Returns 0 on success, a negative error code otherwise.\n  */\n-static inline int odb_source_create_on_disk(struct odb_source *source)\n+static inline int odb_source_create_on_disk(struct odb_source *source,\n+\t\t\t\t\t    const struct odb_create_on_disk_options *opts)\n {\n \tif (!source->create_on_disk)\n \t\treturn 0;\n-\treturn source->create_on_disk(source);\n+\treturn source->create_on_disk(source, opts);\n }\n \n /*\ndiff --git a/setup.c b/setup.c\nindex 8c7b97f92e..37a8e6f124 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,\n \n void create_object_database(struct repository *repo)\n {\n+\tstruct odb_create_on_disk_options opts = { 0 };\n+\n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\n \t * so that the repository can be discovered regardless of the backend\n@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)\n \n \trepo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);\n \n-\tif (odb_source_create_on_disk(repo->objects->sources) < 0)\n+\tif (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)\n \t\tdie(_(\"failed creating object database\"));\n }\n \n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552457","messageId":"20260910-pks-odb-write-alternates-at-creation-time-v5-8-8d10c4238edc@pks.im","threadId":"66216","inReplyTo":"20260910-pks-odb-write-alternates-at-creation-time-v5-0-8d10c4238edc@pks.im","subject":"[PATCH v5 8/9] builtin/clone: write alternates via `odb_create_on_disk()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-10T15:09:18Z","receivedAt":"2026-09-10T15:09:40Z","isPatch":true,"body":"When creating a repository with alternates we first initialize the\nobject database and then write alternates to it in a separate step. This\nis unfortunate due to a couple of reasons:\n\n  - It requires us to have a `write_alternates()` callback, which is\n    unfortunate as we never even write alternates to an object database\n    after it has been created.\n\n  - We're about to make alternates an implementation detail of the\n    object database's backend in a future patch series, so having this\n    callback is suboptimal there.\n\n  - The backend has more flexibility with how exactly alternates are\n    configured when it itself is in full control over their setup at the\n    time where it creates the object database itself.\n\nWe have thus introduced the ability to write alternates right at\ncreation time in the preceding commits, and we have unified setup of\nalternates into a single location. All that's left to do for us now is\nto wire up alternates as an option for the database creation.\n\nDo so.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c   | 5 +----\n builtin/init-db.c | 2 +-\n setup.c           | 7 +++++--\n setup.h           | 6 ++++--\n 4 files changed, 11 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 17353a8e1f..b14264c33a 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1364,11 +1364,8 @@ int cmd_clone(int argc,\n \tif (option_local > 0 && !is_local)\n \t\twarning(_(\"--local is ignored\"));\n \n-\tcreate_object_database(the_repository);\n \tcollect_alternates(&alternates, is_local ? path : NULL);\n-\n-\tfor (size_t i = 0; i < alternates.nr; i++)\n-\t\todb_add_to_alternates_file(the_repository->objects, alternates.v[i]);\n+\tcreate_object_database(the_repository, &alternates);\n \n \ttransport = transport_get(remote, path ? path : remote->url.v[0]);\n \ttransport_set_verbosity(transport, option_verbosity, option_progress);\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex f2c7e3be6d..5c22eae2f3 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -251,7 +251,7 @@ int cmd_init_db(int argc,\n \t\t\t  template_dir, hash_algo, ref_storage_format,\n \t\t\t  init_shared_repository, &reinit);\n \tcreate_reference_database(the_repository, initial_branch, quiet);\n-\tcreate_object_database(the_repository);\n+\tcreate_object_database(the_repository, NULL);\n \n \tif (!quiet) {\n \t\tint len = strlen(git_dir);\ndiff --git a/setup.c b/setup.c\nindex 37a8e6f124..17d0d25973 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,\n \treturn reinit;\n }\n \n-void create_object_database(struct repository *repo)\n+void create_object_database(struct repository *repo,\n+\t\t\t    const struct strvec *alternates)\n {\n-\tstruct odb_create_on_disk_options opts = { 0 };\n+\tstruct odb_create_on_disk_options opts = {\n+\t\t.alternates = alternates,\n+\t};\n \n \t/*\n \t * Create the \"objects\" directory in the common directory. This is done\ndiff --git a/setup.h b/setup.h\nindex f1c1ed65fb..27b2492373 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -291,9 +291,11 @@ void create_reference_database(struct repository *repo, const char *initial_bran\n \n /*\n  * Create the object database for the repository. The repository must have\n- * already been configured properly before calling this function.\n+ * already been configured properly before calling this function. When set,\n+ * `alternates` is the list of alternates that should be written into the\n+ * object database.\n  */\n-void create_object_database(struct repository *repo);\n+void create_object_database(struct repository *repo, const struct strvec *alternates);\n \n /*\n  * NOTE NOTE NOTE!!\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552458","messageId":"20260910-pks-odb-write-alternates-at-creation-time-v5-9-8d10c4238edc@pks.im","threadId":"66216","inReplyTo":"20260910-pks-odb-write-alternates-at-creation-time-v5-0-8d10c4238edc@pks.im","subject":"[PATCH v5 9/9] odb/source: remove the ability to write alternates","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-10T15:09:19Z","receivedAt":"2026-09-10T15:09:42Z","isPatch":true,"body":"There are no users of `odb_source_write_alternates()` in our tree\nanymore. Remove that function and its supporting infrastructure.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n odb.c                 |  9 ---------\n odb.h                 |  7 -------\n odb/source-files.c    | 54 ---------------------------------------------------\n odb/source-inmemory.c |  7 -------\n odb/source-loose.c    |  7 -------\n odb/source-packed.c   |  7 -------\n odb/source.h          | 26 -------------------------\n 7 files changed, 117 deletions(-)\n\ndiff --git a/odb.c b/odb.c\nindex 67d98d64fc..b531cf8fb3 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *\n \treturn alternate;\n }\n \n-void odb_add_to_alternates_file(struct object_database *odb,\n-\t\t\t\tconst char *dir)\n-{\n-\tint ret = odb_source_write_alternate(odb->sources, dir);\n-\tif (ret < 0)\n-\t\tdie(NULL);\n-\todb_add_alternate_recursively(odb, dir, 0);\n-}\n-\n struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,\n \t\t\t\t\t\tconst char *dir)\n {\ndiff --git a/odb.h b/odb.h\nindex b9e0db56ec..2d002461f8 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,\n  */\n int odb_has_alternates(struct object_database *odb);\n \n-/*\n- * Add the directory to the on-disk alternates file; the new entry will also\n- * take effect in the current process.\n- */\n-void odb_add_to_alternates_file(struct object_database *odb,\n-\t\t\t\tconst char *dir);\n-\n /*\n  * Add the directory to the in-memory list of alternate sources (along with any\n  * recursive alternates it points to), but do not modify the on-disk alternates\ndiff --git a/odb/source-files.c b/odb/source-files.c\nindex 8fe65d91f8..b3f340dff8 100644\n--- a/odb/source-files.c\n+++ b/odb/source-files.c\n@@ -306,59 +306,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,\n \treturn 0;\n }\n \n-static int odb_source_files_write_alternate(struct odb_source *source,\n-\t\t\t\t\t    const char *alternate)\n-{\n-\tstruct lock_file lock = LOCK_INIT;\n-\tchar *path = xstrfmt(\"%s/%s\", source->path, \"info/alternates\");\n-\tFILE *in, *out;\n-\tint found = 0;\n-\tint ret;\n-\n-\trepo_hold_lock_file_for_update(source->odb->repo, &lock, path,\n-\t\t\t\t       LOCK_DIE_ON_ERROR);\n-\tout = fdopen_lock_file(&lock, \"w\");\n-\tif (!out) {\n-\t\tret = error_errno(_(\"unable to fdopen alternates lockfile\"));\n-\t\tgoto out;\n-\t}\n-\n-\tin = fopen(path, \"r\");\n-\tif (in) {\n-\t\tstruct strbuf line = STRBUF_INIT;\n-\n-\t\twhile (strbuf_getline(&line, in) != EOF) {\n-\t\t\tif (!strcmp(alternate, line.buf)) {\n-\t\t\t\tfound = 1;\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t\tfprintf_or_die(out, \"%s\\n\", line.buf);\n-\t\t}\n-\n-\t\tstrbuf_release(&line);\n-\t\tfclose(in);\n-\t} else if (errno != ENOENT) {\n-\t\tret = error_errno(_(\"unable to read alternates file\"));\n-\t\tgoto out;\n-\t}\n-\n-\tif (found) {\n-\t\trollback_lock_file(&lock);\n-\t} else {\n-\t\tfprintf_or_die(out, \"%s\\n\", alternate);\n-\t\tif (commit_lock_file(&lock)) {\n-\t\t\tret = error_errno(_(\"unable to move new alternates file into place\"));\n-\t\t\tgoto out;\n-\t\t}\n-\t}\n-\n-\tret = 0;\n-\n-out:\n-\tfree(path);\n-\treturn ret;\n-}\n-\n static int too_many_loose_objects(struct odb_source_files *files, int limit)\n {\n \tunsigned long loose_count;\n@@ -842,7 +789,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,\n \tfiles->base.write_object_stream = odb_source_files_write_object_stream;\n \tfiles->base.begin_transaction = odb_source_files_begin_transaction;\n \tfiles->base.read_alternates = odb_source_files_read_alternates;\n-\tfiles->base.write_alternate = odb_source_files_write_alternate;\n \tfiles->base.optimize = odb_source_files_optimize;\n \tfiles->base.optimize_required = odb_source_files_optimize_required;\n \ndiff --git a/odb/source-inmemory.c b/odb/source-inmemory.c\nindex 795672adf2..b00248dfb2 100644\n--- a/odb/source-inmemory.c\n+++ b/odb/source-inmemory.c\n@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t       const char *alternate UNUSED)\n-{\n-\treturn error(\"in-memory source does not support alternates\");\n-}\n-\n static void odb_source_inmemory_close(struct odb_source *source UNUSED)\n {\n }\n@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)\n \tsource->base.freshen_object = odb_source_inmemory_freshen_object;\n \tsource->base.begin_transaction = odb_source_inmemory_begin_transaction;\n \tsource->base.read_alternates = odb_source_inmemory_read_alternates;\n-\tsource->base.write_alternate = odb_source_inmemory_write_alternate;\n \n \treturn source;\n }\ndiff --git a/odb/source-loose.c b/odb/source-loose.c\nindex bb3455dfbd..0f9b30bac1 100644\n--- a/odb/source-loose.c\n+++ b/odb/source-loose.c\n@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t    const char *alternate UNUSED)\n-{\n-\treturn error(\"loose source does not support alternates\");\n-}\n-\n static void odb_source_loose_clear_cache(struct odb_source_loose *loose)\n {\n \toidtree_clear(loose->cache);\n@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,\n \tloose->base.write_object_stream = odb_source_loose_write_object_stream;\n \tloose->base.begin_transaction = odb_source_loose_begin_transaction;\n \tloose->base.read_alternates = odb_source_loose_read_alternates;\n-\tloose->base.write_alternate = odb_source_loose_write_alternate;\n \n \tif (!is_absolute_path(loose->base.path))\n \t\tchdir_notify_register(NULL, odb_source_loose_reparent, loose);\ndiff --git a/odb/source-packed.c b/odb/source-packed.c\nindex 630d955585..c2d253759c 100644\n--- a/odb/source-packed.c\n+++ b/odb/source-packed.c\n@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,\n \treturn 0;\n }\n \n-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,\n-\t\t\t\t\t     const char *alternate UNUSED)\n-{\n-\treturn error(\"packed backend cannot write alternates\");\n-}\n-\n void (*report_garbage)(unsigned seen_bits, const char *path);\n \n static void report_helper(const struct string_list *list,\n@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,\n \tpacked->base.write_object_stream = odb_source_packed_write_object_stream;\n \tpacked->base.begin_transaction = odb_source_packed_begin_transaction;\n \tpacked->base.read_alternates = odb_source_packed_read_alternates;\n-\tpacked->base.write_alternate = odb_source_packed_write_alternate;\n \n \tif (!is_absolute_path(path))\n \t\tchdir_notify_register(NULL, odb_source_packed_reparent, packed);\ndiff --git a/odb/source.h b/odb/source.h\nindex 63f1c0c531..693a9fc604 100644\n--- a/odb/source.h\n+++ b/odb/source.h\n@@ -286,19 +286,6 @@ struct odb_source {\n \tint (*read_alternates)(struct odb_source *source,\n \t\t\t       struct strvec *out);\n \n-\t/*\n-\t * This callback is expected to persist the singular alternate passed\n-\t * to it into its list of alternates. Any pre-existing alternates are\n-\t * expected to remain active. Subsequent calls to `read_alternates` are\n-\t * thus expected to yield the pre-existing list of alternates plus the\n-\t * newly added alternate appended to its end.\n-\t *\n-\t * The callback is expected to return 0 on success, a negative error\n-\t * code otherwise.\n-\t */\n-\tint (*write_alternate)(struct odb_source *source,\n-\t\t\t       const char *alternate);\n-\n \t/*\n \t * This callback is expected to optimize the object database source.\n \t * Returns 0 on success, a negative error code otherwise.\n@@ -518,19 +505,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,\n \treturn source->read_alternates(source, out);\n }\n \n-/*\n- * Write and persist a new alternate object database source for the given\n- * source. Any preexisting alternates are expected to stay valid, and the new\n- * alternate shall be appended to the end of the list.\n- *\n- * Returns 0 on success, a negative error code otherwise.\n- */\n-static inline int odb_source_write_alternate(struct odb_source *source,\n-\t\t\t\t\t      const char *alternate)\n-{\n-\treturn source->write_alternate(source, alternate);\n-}\n-\n /*\n  * Create a new transaction that can be used to write objects into a temporary\n  * staging area. The objects will only be persisted when the transaction is\n\n-- \n2.55.0.1074.ge7621b4bad.dirty\n\n"},{"id":"552488","messageId":"CAOLa=ZRYsJL_0sKnfHD0PJO+5c+BKSMiuN20PeQHKJin82TJDw@mail.gmail.com","threadId":"66216","inReplyTo":"20260910-pks-odb-write-alternates-at-creation-time-v5-0-8d10c4238edc@pks.im","subject":"Re: [PATCH v5 0/9] odb: write alternates at creation time","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-09-10T19:53:21Z","receivedAt":"2026-09-10T19:53:24Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Hi,\n>\n> writing alternates into the object database currently happens via\n> `odb_source_write_alternate()`. But while that creates the ability to\n> create alternates at arbitrary points of a source's lifetime, we don't\n> use that functionality in the first place. Instead, we only ever write\n> alternates when creating a new repository.\n>\n> This design is suboptimal due to a couple of reasons:\n>\n>   - It requires us to have a `write_alternates()` callback, which is\n>     overblown as we never even write alternates to an object database\n>     after it has been created.\n>\n>   - We're about to make alternates an implementation detail of the\n>     object database's backend in a future patch series, so alternate\n>     implementations may not even support them.\n>\n>   - The backend has more flexibility with how exactly alternates are\n>     configured when it itself is in full control over their setup at the\n>     time where it creates the object database itself.\n>\n> This patch series thus refactors how we handle alternates so that we\n> don't write them ad-hoc anymore. Instead, the series introduces a new\n> option for `odb_source_create_on_disk()` that makes it handle those\n> alternates at creation time.\n>\n> This is part of the bigger goal of moving handling of alternates into\n> the \"files\" backend.\n>\n> This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)\n> with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop\n> `alternates_db` field, 2026-08-17) merged into it.\n>\n> Changes in v5:\n>   - Rename `add_one_alternate` and `add_one_alternate_data` to\n>     `collect_one_alternate` and `collect_alternates_data` to clarify\n>     their intent a bit.\n>   - Drop extra parameter in `collect_alternates()`.\n>   - Clarify why we compute `commondir` even though it's unused.\n>   - Link to v4: https://patch.msgid.link/20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im\n>\n\nLooks like you missed my comment/question in\nCAOLa=ZQaPstiQmXm9=TyWPUxL6X2=Lcqeg6y2XeXzSJDpq-GBA@mail.gmail.com, but\notherwise looks good :)\n\n[snip]\n"},{"id":"552499","messageId":"aqOOYI9v8M9VYeNN@pks.im","threadId":"66216","inReplyTo":"CAOLa=ZQaPstiQmXm9=TyWPUxL6X2=Lcqeg6y2XeXzSJDpq-GBA@mail.gmail.com","subject":"Re: [PATCH v4 7/9] odb/source: support writing alternates when creating the database","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:15:12Z","receivedAt":"2026-09-11T05:15:18Z","isPatch":true,"body":"On Thu, Sep 10, 2026 at 04:10:31AM -0700, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/odb/source-files.c b/odb/source-files.c\n> > index b7b3a297bb..8fe65d91f8 100644\n> > --- a/odb/source-files.c\n> > +++ b/odb/source-files.c\n> > @@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)\n[snip]\n> > +\t\t/*\n> > +\t\t * The alternates file may already exist, e.g. when it has been\n> > +\t\t * seeded from a template directory. Read any preexisting\n> > +\t\t * entries so that we don't end up writing duplicates.\n> > +\t\t */\n> > +\t\torig = fopen(path.buf, \"r\");\n> > +\t\tif (orig) {\n> > +\t\t\twhile (strbuf_getline(&line, orig) != EOF) {\n> > +\t\t\t\tstrset_add(&seen, line.buf);\n> > +\t\t\t\tfprintf(alternates, \"%s\\n\", line.buf);\n> > +\t\t\t}\n> > +\n> > +\t\t\tif (ferror(orig)) {\n> > +\t\t\t\tret = error_errno(_(\"unable to read alternates file\"));\n> > +\t\t\t\tfclose(orig);\n> > +\t\t\t\tgoto out;\n> > +\t\t\t}\n> \n> Shouldn't this be checked inside the for loop with every `fprintf` call?\n\nThere isn't really any need to, as the error indicator on file streams\nis sticky. Sure, it would allow us to potentially abort earlier. But\nit's unlikely that this really matters in practice.\n\n> > diff --git a/odb/source.h b/odb/source.h\n> > index ea8675247e..63f1c0c531 100644\n> > --- a/odb/source.h\n> > +++ b/odb/source.h\n> > @@ -36,6 +36,15 @@ struct object_id;\n> >  struct odb_stream;\n> >  struct strvec;\n> >\n> > +struct odb_create_on_disk_options {\n> > +\t/*\n> > +\t * Alternates that shall be written into the newly created object\n> > +\t * database. Whether or not this option can be handled is specific to\n> > +\t * the backend.\n> > +\t */\n> \n> Would it make sense to formalize errors thrown by backends, so we know\n> when a backend specifically cannot handle alternates?\n\nMaybe, but there's nothing that'd use it. So I'm a bit hesitant to\nintroduce that right now without us having a way to verify the logic at\nall. We may want to eventually introduce such logic though.\n\nPatrick\n"},{"id":"552500","messageId":"aqOOZB0wQ8FNGVE6@pks.im","threadId":"66216","inReplyTo":"CAOLa=ZRYsJL_0sKnfHD0PJO+5c+BKSMiuN20PeQHKJin82TJDw@mail.gmail.com","subject":"Re: [PATCH v5 0/9] odb: write alternates at creation time","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-09-11T05:15:16Z","receivedAt":"2026-09-11T05:15:21Z","isPatch":true,"body":"On Thu, Sep 10, 2026 at 12:53:21PM -0700, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > Hi,\n> >\n> > writing alternates into the object database currently happens via\n> > `odb_source_write_alternate()`. But while that creates the ability to\n> > create alternates at arbitrary points of a source's lifetime, we don't\n> > use that functionality in the first place. Instead, we only ever write\n> > alternates when creating a new repository.\n> >\n> > This design is suboptimal due to a couple of reasons:\n> >\n> >   - It requires us to have a `write_alternates()` callback, which is\n> >     overblown as we never even write alternates to an object database\n> >     after it has been created.\n> >\n> >   - We're about to make alternates an implementation detail of the\n> >     object database's backend in a future patch series, so alternate\n> >     implementations may not even support them.\n> >\n> >   - The backend has more flexibility with how exactly alternates are\n> >     configured when it itself is in full control over their setup at the\n> >     time where it creates the object database itself.\n> >\n> > This patch series thus refactors how we handle alternates so that we\n> > don't write them ad-hoc anymore. Instead, the series introduces a new\n> > option for `odb_source_create_on_disk()` that makes it handle those\n> > alternates at creation time.\n> >\n> > This is part of the bigger goal of moving handling of alternates into\n> > the \"files\" backend.\n> >\n> > This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)\n> > with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop\n> > `alternates_db` field, 2026-08-17) merged into it.\n> >\n> > Changes in v5:\n> >   - Rename `add_one_alternate` and `add_one_alternate_data` to\n> >     `collect_one_alternate` and `collect_alternates_data` to clarify\n> >     their intent a bit.\n> >   - Drop extra parameter in `collect_alternates()`.\n> >   - Clarify why we compute `commondir` even though it's unused.\n> >   - Link to v4: https://patch.msgid.link/20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im\n> >\n> \n> Looks like you missed my comment/question in\n> CAOLa=ZQaPstiQmXm9=TyWPUxL6X2=Lcqeg6y2XeXzSJDpq-GBA@mail.gmail.com, but\n> otherwise looks good :)\n\nOh, indeed, I somehow overlooked that mail. Replied to it now, but I\ndon't think it requires further changes. Thanks!\n\nPatrick\n"},{"id":"552692","messageId":"CAOLa=ZSH=Ozzk-870-wfQ7zYhgKcUnmLVOcHxCv1n7NVm8QTcA@mail.gmail.com","threadId":"66216","inReplyTo":"aqOOZB0wQ8FNGVE6@pks.im","subject":"Re: [PATCH v5 0/9] odb: write alternates at creation time","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-09-14T10:22:36Z","receivedAt":"2026-09-14T10:22:44Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Thu, Sep 10, 2026 at 12:53:21PM -0700, Karthik Nayak wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>>\n>> > Hi,\n>> >\n>> > writing alternates into the object database currently happens via\n>> > `odb_source_write_alternate()`. But while that creates the ability to\n>> > create alternates at arbitrary points of a source's lifetime, we don't\n>> > use that functionality in the first place. Instead, we only ever write\n>> > alternates when creating a new repository.\n>> >\n>> > This design is suboptimal due to a couple of reasons:\n>> >\n>> >   - It requires us to have a `write_alternates()` callback, which is\n>> >     overblown as we never even write alternates to an object database\n>> >     after it has been created.\n>> >\n>> >   - We're about to make alternates an implementation detail of the\n>> >     object database's backend in a future patch series, so alternate\n>> >     implementations may not even support them.\n>> >\n>> >   - The backend has more flexibility with how exactly alternates are\n>> >     configured when it itself is in full control over their setup at the\n>> >     time where it creates the object database itself.\n>> >\n>> > This patch series thus refactors how we handle alternates so that we\n>> > don't write them ad-hoc anymore. Instead, the series introduces a new\n>> > option for `odb_source_create_on_disk()` that makes it handle those\n>> > alternates at creation time.\n>> >\n>> > This is part of the bigger goal of moving handling of alternates into\n>> > the \"files\" backend.\n>> >\n>> > This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)\n>> > with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop\n>> > `alternates_db` field, 2026-08-17) merged into it.\n>> >\n>> > Changes in v5:\n>> >   - Rename `add_one_alternate` and `add_one_alternate_data` to\n>> >     `collect_one_alternate` and `collect_alternates_data` to clarify\n>> >     their intent a bit.\n>> >   - Drop extra parameter in `collect_alternates()`.\n>> >   - Clarify why we compute `commondir` even though it's unused.\n>> >   - Link to v4: https://patch.msgid.link/20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im\n>> >\n>>\n>> Looks like you missed my comment/question in\n>> CAOLa=ZQaPstiQmXm9=TyWPUxL6X2=Lcqeg6y2XeXzSJDpq-GBA@mail.gmail.com, but\n>> otherwise looks good :)\n>\n> Oh, indeed, I somehow overlooked that mail. Replied to it now, but I\n> don't think it requires further changes. Thanks!\n>\n> Patrick\n\nWith your response, I have nothing more to add! Thanks\n"}]}