Volume XXII, number 279Tuesday, October 6, 2026Latest message 45 minutes ago

The Git List

News and archive of git@vger.kernel.org, since April 2005

patch, 8 partsodb: write alternates at creation time

85 messages between Aug 25, 2026 and Sep 14, 2026, from Patrick Steinhardt, Toon Claes, Junio C Hamano, Justin Tobler, Karthik Nayak.

Plain Markdown or JSON for tools and agents. Diffs are folded; open one to read it.

Patrick SteinhardtAug 25, 2026, 14:11 UTC on lore
Hi,

writing alternates into the object database currently happens via `odb_source_write_alternate()`. But while that creates the ability to create alternates at arbitrary points of a source's lifetime, we don't use that functionality in the first place. Instead, we only ever write alternates when creating a new repository.

This design is suboptimal due to a couple of reasons:
  - It requires us to have a `write_alternates()` callback, which is
    overblown as we never even write alternates to an object database
    after it has been created.
  - We're about to make alternates an implementation detail of the
    object database's backend in a future patch series, so alternate
    implementations may not even support them.
  - The backend has more flexibility with how exactly alternates are
    configured when it itself is in full control over their setup at the
    time where it creates the object database itself.

This patch series thus refactors how we handle alternates so that we don't write them ad-hoc anymore. Instead, the series introduces a new option for `odb_source_create_on_disk()` that makes it handle those alternates at creation time.

This is part of the bigger goal of moving handling of alternates into the "files" backend.

This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24) with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop `alternates_db` field, 2026-08-17) merged into it.

Thanks!
Patrick
---
Patrick Steinhardt (8):
      builtin/clone: defer setup of the object database
      builtin/clone: move around `setup_reference()`
      builtin/clone: refactor handling of "--reference{,-if-able}"
      builtin/clone: move setup of alternates for shared local clones
      builtin/clone: move setup of alternates for non-shared local clones
      odb/source: support writing alternates when creating the database
      builtin/clone: write alternates via `odb_create_on_disk()`
      odb/source: remove the ability to write alternates
 builtin/clone.c            | 108 ++++++++++++++++++++++++--------------
 odb.c                      |   9 ----
 odb.h                      |   7 ---
 odb/source-files.c         | 128 +++++++++++++++++++++++++--------------------
 odb/source-inmemory.c      |   7 ---
 odb/source-loose.c         |   7 ---
 odb/source-packed.c        |   7 ---
 odb/source.h               |  44 ++++++----------
 setup.c                    |  15 ++++--
 setup.h                    |  15 ++++--
 t/t5604-clone-reference.sh |  25 +++++++++
 11 files changed, 201 insertions(+), 171 deletions(-)

--- base-commit: afa255aeb620346d56a2c01fb5ae9163513c56d7 change-id: 20260813-pks-odb-write-alternates-at-creation-time-64010deb94a0

Patrick SteinhardtAug 25, 2026, 14:11 UTC in reply to Patrick Steinhardt on lore

[PATCH 1/8] builtin/clone: defer setup of the object database

When cloning a repository we defer initialization of the reference database. This is because we don't yet know all details required for us to initialize the refdb in the first place. Most importantly, what we are missing is information about the object hash.

We don't do the same thing for the object database yet, but here we essentially have the same problem. While the "files" database does not need any information about the object format at creation time, alternate backends are likely to require that information so that they can properly set up their data structures.

Besides this forward-looking future proofing though, we also have a second use case for deferring initialization of the object database, namely alternates. When initializing the object database we do not yet know whether we'll need alternates or not because this depends on the repository we're about to clone from. If it is a local repository and the user has passed "--refernce{,-if-able}", then we will end up writing alternates into the object database.

The ugly part though is that we cannot determine where the repository is getting cloned from before it has been initialized. While we of course already have access to the user-provided URI, that URI can be very well rewritten via "url.<base>.insteadOf". We can of course read the global- and system-level configuration to resolve it. But we explicitly resolve the URI a second time after we have initialized the repository because it can happen that we copy a ".git/config" over from our templates, and that file may cause us to rewrite the path.

In a subsequent commit though we'll start to write alternates as part of the repository initialization, so we'll need to have the URI properly resolved before we can initialize the object database. This is ugly, but as mentioned above it makes sense for us to defer its initialization anyway so that we also know about the object hash already.

Introduce a new flag that makes `init_db()` skip initializing the object database. Expose `create_object_database()` and make use of it after we have resolved the URI.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 12 ++++++++----
 setup.c         |  8 +++++---
 setup.h         | 15 ++++++++++-----
 3 files changed, 23 insertions(+), 12 deletions(-)
Show changes to 3 files +23 −12

builtin/clone.c, setup.c, setup.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 5b25cca510..0a67492ebd 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1184,11 +1184,14 @@ int cmd_clone(int argc,
 	 * database. We do not yet know about the object format of the
 	 * repository, and reference backends may persist that information into
 	 * their on-disk data structures.
+	 *
+	 * Furthermore, we skip initializing the object database so that we can
+	 * first resolve potential alternates before creating it.
 	 */
 	init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
 		GIT_HASH_UNKNOWN, ref_storage_format, NULL,
 		do_not_override_repo_unix_permissions,
-		INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
+		INIT_DB_QUIET | INIT_DB_SKIP_REFDB | INIT_DB_SKIP_ODB);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
@@ -1311,9 +1314,6 @@ int cmd_clone(int argc,
 		strbuf_reset(&key);
 	}
 
-	if (option_required_reference.nr || option_optional_reference.nr)
-		setup_reference();
-
 	remote = remote_get_early(remote_name);
 
 	if (!option_rev)
@@ -1342,6 +1342,10 @@ int cmd_clone(int argc,
 	if (option_local > 0 && !is_local)
 		warning(_("--local is ignored"));
 
+	create_object_database(the_repository);
+	if (option_required_reference.nr || option_optional_reference.nr)
+		setup_reference();
+
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
 	transport->family = family;
diff --git a/setup.c b/setup.c
index d90654f584..e654e27d05 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,
 	return reinit;
 }
 
-static void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo)
 {
 	/*
 	 * Create the "objects" directory in the common directory. This is done
@@ -2829,7 +2829,8 @@ int init_db(struct repository *repo,
 	    const char *template_dir, int hash,
 	    enum ref_storage_format ref_storage_format,
 	    const char *initial_branch,
-	    int init_shared_repository, unsigned int flags)
+	    int init_shared_repository,
+	    enum init_db_flags flags)
 {
 	int reinit;
 	int exist_ok = flags & INIT_DB_EXIST_OK;
@@ -2903,7 +2904,8 @@ int init_db(struct repository *repo,
 
 	if (!(flags & INIT_DB_SKIP_REFDB))
 		create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
-	create_object_database(repo);
+	if (!(flags & INIT_DB_SKIP_ODB))
+		create_object_database(repo);
 
 	startup_info->have_repository = 1;
 
diff --git a/setup.h b/setup.h
index 763fd384e8..570ebcd150 100644
--- a/setup.h
+++ b/setup.h
@@ -256,9 +256,12 @@ int apply_repository_format(struct repository *repo,
 
 const char *get_template_dir(const char *option_template);
 
-#define INIT_DB_QUIET      (1 << 0)
-#define INIT_DB_EXIST_OK   (1 << 1)
-#define INIT_DB_SKIP_REFDB (1 << 2)
+enum init_db_flags {
+	INIT_DB_QUIET = (1 << 0),
+	INIT_DB_EXIST_OK = (1 << 1),
+	INIT_DB_SKIP_REFDB = (1 << 2),
+	INIT_DB_SKIP_ODB = (1 << 3),
+};
 
 int init_db(struct repository *repo,
 	    const char *git_dir,
@@ -266,13 +269,15 @@ int init_db(struct repository *repo,
 	    const char *worktree,
 	    const char *template_dir, int hash_algo,
 	    enum ref_storage_format ref_storage_format,
-	    const char *initial_branch, int init_shared_repository,
-	    unsigned int flags);
+	    const char *initial_branch,
+	    int init_shared_repository,
+	    enum init_db_flags flags);
 void initialize_repository_version(struct repository *repo,
 				   int hash_algo,
 				   enum ref_storage_format ref_storage_format,
 				   int reinit);
 void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
+void create_object_database(struct repository *repo);
 
 /*
  * NOTE NOTE NOTE!!
-- 
2.55.0.822.g20453c30eb.dirty
Patrick SteinhardtAug 25, 2026, 14:11 UTC in reply to Patrick Steinhardt on lore

[PATCH 2/8] builtin/clone: move around `setup_reference()`

In a subsequent commit, `setup_reference()` will start to call `copy_alternates()`. Prepare for this by moving the function further down so that we can avoid adding a declaration.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)
Show changes to builtin/clone.c +10 −10
diff --git a/builtin/clone.c b/builtin/clone.c
index 0a67492ebd..8c990ce0cc 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
 	return 0;
 }
 
-static void setup_reference(void)
-{
-	int required = 1;
-	for_each_string_list(&option_required_reference,
-			     add_one_reference, &required);
-	required = 0;
-	for_each_string_list(&option_optional_reference,
-			     add_one_reference, &required);
-}
-
 static void copy_alternates(struct strbuf *src, const char *src_repo)
 {
 	/*
@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
+static void setup_reference(void)
+{
+	int required = 1;
+	for_each_string_list(&option_required_reference,
+			     add_one_reference, &required);
+	required = 0;
+	for_each_string_list(&option_optional_reference,
+			     add_one_reference, &required);
+}
+
 static void mkdir_if_missing(const char *pathname, mode_t mode)
 {
 	struct stat st;
-- 
2.55.0.822.g20453c30eb.dirty
Patrick SteinhardtAug 25, 2026, 14:11 UTC in reply to Patrick Steinhardt on lore

[PATCH 3/8] builtin/clone: refactor handling of "--reference{,-if-able}"

Users can pass "--reference{,-if-able}" to git-clone(1) to instruct it to set up alternates for the newly created repository. This allows it to reuse objects from the source repository so that in the best case we don't have to clone all objects over.

Those options are handled by the confusingly named `setup_reference()` function -- without the above context, one might rightfully believe that it was about refs, not about alternates. The function itself is rather simple: we loop through all provided alternate paths and then, if such an alternate is valid, we write it to the object database.

In subsequent commits we're about to consolidate the complete setup of alternates into this function, and furthermore we'll refactor the setup of the object database to handle doing this for us instead of writing the alterantes into it one by one.

Prepare for this refactoring by collecting the alternates into a strvec. Rename the function to `collect_alternates()` to clarify its scope.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------
 1 file changed, 28 insertions(+), 16 deletions(-)
Show changes to builtin/clone.c +28 −16
diff --git a/builtin/clone.c b/builtin/clone.c
index 8c990ce0cc..8eae3ac7d9 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
 	return canon;
 }
 
-static int add_one_reference(struct string_list_item *item, void *cb_data)
+struct add_one_alternate_data {
+	struct strvec *alternates;
+	int required;
+};
+
+static int add_one_alternate(struct string_list_item *item, void *cb_data)
 {
+	struct add_one_alternate_data *data = cb_data;
 	struct strbuf err = STRBUF_INIT;
-	int *required = cb_data;
 	char *ref_git = compute_alternate_path(item->string, &err);
 
 	if (!ref_git) {
-		if (*required)
+		if (data->required)
 			die("%s", err.buf);
 		else
 			fprintf(stderr,
 				_("info: Could not add alternate for '%s': %s\n"),
 				item->string, err.buf);
 	} else {
-		struct strbuf sb = STRBUF_INIT;
-		strbuf_addf(&sb, "%s/objects", ref_git);
-		odb_add_to_alternates_file(the_repository->objects, sb.buf);
-		strbuf_release(&sb);
+		strvec_pushf(data->alternates, "%s/objects", ref_git);
 	}
 
 	strbuf_release(&err);
@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
-static void setup_reference(void)
+static void collect_alternates(struct strvec *alternates)
 {
-	int required = 1;
-	for_each_string_list(&option_required_reference,
-			     add_one_reference, &required);
-	required = 0;
-	for_each_string_list(&option_optional_reference,
-			     add_one_reference, &required);
+	if (option_required_reference.nr || option_optional_reference.nr) {
+		struct add_one_alternate_data data = {
+			.alternates = alternates,
+			.required = 1,
+		};
+
+		for_each_string_list(&option_required_reference,
+				     add_one_alternate, &data);
+		data.required = 0;
+		for_each_string_list(&option_optional_reference,
+				     add_one_alternate, &data);
+	}
 }
 
 static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -999,6 +1007,7 @@ int cmd_clone(int argc,
 			   N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
 		OPT_END()
 	};
+	struct strvec alternates = STRVEC_INIT;
 
 	const char * const builtin_clone_usage[] = {
 		N_("git clone [<options>] [--] <repo> [<dir>]"),
@@ -1343,8 +1352,10 @@ int cmd_clone(int argc,
 		warning(_("--local is ignored"));
 
 	create_object_database(the_repository);
-	if (option_required_reference.nr || option_optional_reference.nr)
-		setup_reference();
+	collect_alternates(&alternates);
+
+	for (size_t i = 0; i < alternates.nr; i++)
+		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
 
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
@@ -1641,6 +1652,7 @@ int cmd_clone(int argc,
 	string_list_clear(&option_not, 0);
 	string_list_clear(&option_config, 0);
 	string_list_clear(&server_options, 0);
+	strvec_clear(&alternates);
 
 	free(remote_name);
 	strbuf_release(&reflog_msg);
-- 
2.55.0.822.g20453c30eb.dirty
Patrick SteinhardtAug 25, 2026, 14:11 UTC in reply to Patrick Steinhardt on lore

[PATCH 4/8] builtin/clone: move setup of alternates for shared local clones

When cloning a local repository with "--shared" we add that repository to the new repository's alternates. This is done in `clone_local()`, which is responsible for performing local clones.

Move the logic into `collect_alternates()` to unify our setup of alternates. Furthermore, this will allow us to set up alternates right at creation time of the object database.

Note that the logic for cloning a local repository with "--no-shared" is not yet part of `collect_alternates()`. This will be handled in the next commit.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)
Show changes to builtin/clone.c +14 −9
diff --git a/builtin/clone.c b/builtin/clone.c
index 8eae3ac7d9..08c8f5a94f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
-static void collect_alternates(struct strvec *alternates)
+static void collect_alternates(struct strvec *alternates,
+			       const char *src_repo, bool is_local)
 {
 	if (option_required_reference.nr || option_optional_reference.nr) {
 		struct add_one_alternate_data data = {
@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
 		for_each_string_list(&option_optional_reference,
 				     add_one_alternate, &data);
 	}
+
+	if (is_local) {
+		struct strbuf commondir = STRBUF_INIT;
+
+		get_common_dir(&commondir, src_repo);
+		if (option_shared)
+			strvec_pushf(alternates, "%s/objects", commondir.buf);
+
+		strbuf_release(&commondir);
+	}
 }
 
 static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 
 static void clone_local(const char *src_repo, const char *dest_repo)
 {
-	if (option_shared) {
-		struct strbuf alt = STRBUF_INIT;
-		get_common_dir(&alt, src_repo);
-		strbuf_addstr(&alt, "/objects");
-		odb_add_to_alternates_file(the_repository->objects, alt.buf);
-		strbuf_release(&alt);
-	} else {
+	if (!option_shared) {
 		struct strbuf src = STRBUF_INIT;
 		struct strbuf dest = STRBUF_INIT;
 		get_common_dir(&src, src_repo);
@@ -1352,7 +1357,7 @@ int cmd_clone(int argc,
 		warning(_("--local is ignored"));
 
 	create_object_database(the_repository);
-	collect_alternates(&alternates);
+	collect_alternates(&alternates, path, is_local);
 
 	for (size_t i = 0; i < alternates.nr; i++)
 		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
-- 
2.55.0.822.g20453c30eb.dirty
Patrick SteinhardtAug 25, 2026, 14:11 UTC in reply to Patrick Steinhardt on lore

[PATCH 5/8] builtin/clone: move setup of alternates for non-shared local clones

Similar as in the preceding commit, move the setup of alternates for local clones with "--no-shared" into `collect_alternates()`. With this step, the complete setup of alternates is now handled by that function.

Note that besides moving stuff around, it also fixes a bug: previously, we did not know to resolve the referenced repository's common directory. Consequently, when referencing a worktree we failed to resolve alternates. But as `collect_alternates()` already knows to resolve the commondir for "--local" we can simply reuse this resolved path for our purpose.

Add two tests, the first one of which exercises this bug to avoid future regressions. The second patch ensures that we properly handle relative alternates for a referenced worktree.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c            | 34 +++++++++++++++++++++++-----------
 t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++
 2 files changed, 48 insertions(+), 11 deletions(-)
Show changes to 2 files +48 −11

builtin/clone.c, t/t5604-clone-reference.sh

diff --git a/builtin/clone.c b/builtin/clone.c
index 08c8f5a94f..2e3473fddf 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)
 	return 0;
 }
 
-static void copy_alternates(struct strbuf *src, const char *src_repo)
+static void read_alternates(struct strvec *alternates, const char *src_repo)
 {
 	/*
 	 * Read from the source objects/info/alternates file
@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	 * to turn entries with paths relative to the original
 	 * absolute, so that they can be used in the new repository.
 	 */
-	FILE *in = xfopen(src->buf, "r");
+	FILE *in;
+	struct strbuf path = STRBUF_INIT;
 	struct strbuf line = STRBUF_INIT;
 
+	strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
+
+	in = fopen(path.buf, "r");
+	if (!in) {
+		if (errno == ENOENT)
+			goto out;
+		die_errno("could not read alternates file '%s'", path.buf);
+	}
+
 	while (strbuf_getline(&line, in) != EOF) {
 		char *abs_path;
 		if (!line.len || line.buf[0] == '#')
 			continue;
 		if (is_absolute_path(line.buf)) {
-			odb_add_to_alternates_file(the_repository->objects,
-						   line.buf);
+			strvec_push(alternates, line.buf);
 			continue;
 		}
 		abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
 		if (!normalize_path_copy(abs_path, abs_path))
-			odb_add_to_alternates_file(the_repository->objects,
-						   abs_path);
+			strvec_push(alternates, abs_path);
 		else
 			warning("skipping invalid relative alternate: %s/%s",
 				src_repo, line.buf);
 		free(abs_path);
 	}
+
+out:
+	strbuf_release(&path);
 	strbuf_release(&line);
-	fclose(in);
+	if (in)
+		fclose(in);
 }
 
 static void collect_alternates(struct strvec *alternates,
@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,
 		get_common_dir(&commondir, src_repo);
 		if (option_shared)
 			strvec_pushf(alternates, "%s/objects", commondir.buf);
+		else
+			read_alternates(alternates, commondir.buf);
 
 		strbuf_release(&commondir);
 	}
@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 			continue;
 		}
 
-		/* Files that cannot be copied bit-for-bit... */
-		if (!fspathcmp(iter->relative_path, "info/alternates")) {
-			copy_alternates(src, src_repo);
+		/* Alternates were already handled earlier. */
+		if (!fspathcmp(iter->relative_path, "info/alternates"))
 			continue;
-		}
 
 		if (unlink(dest->buf) && errno != ENOENT)
 			die_errno(_("failed to unlink '%s'"), dest->buf);
diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
index 39a0c318df..9e4b98fdb8 100755
--- a/t/t5604-clone-reference.sh
+++ b/t/t5604-clone-reference.sh
@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '
 	git clone --no-local --reference graph.git --dissociate orig clone
 '
 
+test_expect_success 'local clone from linked worktree carries over alternates' '
+	rm -fr base derived derived-wt dst expect &&
+	git init base &&
+	test_commit -C base one &&
+	git clone --shared base derived &&
+	git -C derived worktree add ../derived-wt &&
+	git clone derived-wt dst &&
+	echo "$(pwd)/base/.git/objects" >expect &&
+	test_cmp expect dst/.git/objects/info/alternates &&
+	git -C dst fsck
+'
+
+test_expect_success 'local clone from linked worktree resolves relative alternates' '
+	rm -fr base derived derived-wt dst expect &&
+	git init base &&
+	test_commit -C base one &&
+	git clone --shared base derived &&
+	echo "../../../base/.git/objects" >derived/.git/objects/info/alternates &&
+	git -C derived worktree add ../derived-wt &&
+	git clone derived-wt dst &&
+	echo "$(pwd)/base/.git/objects" >expect &&
+	test_cmp expect dst/.git/objects/info/alternates &&
+	git -C dst fsck
+'
+
 test_done
-- 
2.55.0.822.g20453c30eb.dirty
Patrick SteinhardtAug 25, 2026, 14:11 UTC in reply to Patrick Steinhardt on lore

[PATCH 6/8] odb/source: support writing alternates when creating the database

Add the ability to write alternates when creating the object database. This change allows us to remove the `write_alternates()` callback in a subsequent patch.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb/source-files.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 odb/source.h       | 18 +++++++++++---
 setup.c            |  4 ++-
 3 files changed, 89 insertions(+), 6 deletions(-)
Show changes to 3 files +89 −6

odb/source-files.c, odb/source.h, setup.c

diff --git a/odb/source-files.c b/odb/source-files.c
index b7b3a297bb..5e77b21d9f 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -18,6 +18,7 @@
 #include "run-command.h"
 #include "strbuf.h"
 #include "string-list.h"
+#include "strmap.h"
 #include "strvec.h"
 #include "tree.h"
 #include "write-or-die.h"
@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)
 	odb_source_close(&files->packed->base);
 }
 
-static int odb_source_files_create_on_disk(struct odb_source *source)
+static int odb_source_files_create_on_disk(struct odb_source *source,
+					   const struct odb_create_on_disk_options *opts)
 {
 	struct strbuf path = STRBUF_INIT;
+	struct strset seen = STRSET_INIT;
+	struct strbuf line = STRBUF_INIT;
+	FILE *f = NULL;
+	int ret;
 
 	safe_create_dir(source->odb->repo, source->path, 1);
 
@@ -64,8 +70,71 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
 	strbuf_addf(&path, "%s/info", source->path);
 	safe_create_dir(source->odb->repo, path.buf, 1);
 
+	if (opts->alternates && opts->alternates->nr) {
+		strbuf_reset(&path);
+		strbuf_addf(&path, "%s/info/alternates", source->path);
+
+		/*
+		 * The alternates file may already exist, e.g. when it has been
+		 * seeded from a template directory. Read any preexisting
+		 * entries so that we don't end up writing duplicates.
+		 */
+		f = fopen(path.buf, "r");
+		if (f) {
+			while (strbuf_getline(&line, f) != EOF)
+				strset_add(&seen, line.buf);
+
+			if (ferror(f)) {
+				ret = error_errno(_("unable to read alternates file"));
+				goto out;
+			}
+
+			fclose(f);
+		} else if (errno != ENOENT) {
+			ret = error_errno(_("unable to read alternates file"));
+			goto out;
+		}
+
+		f = fopen(path.buf, "a");
+		if (!f) {
+			ret = error_errno(_("unable to open alternates file for writing"));
+			goto out;
+		}
+
+		for (size_t i = 0; i < opts->alternates->nr; i++) {
+			const char *alternate = opts->alternates->v[i];
+
+			if (!strset_add(&seen, alternate))
+				continue;
+
+			fprintf(f, "%s\n", alternate);
+		}
+
+		if (ferror(f)) {
+			ret = error_errno(_("unable to write alternates file"));
+			goto out;
+		}
+
+		ret = fclose(f);
+		f = NULL;
+		if (ret) {
+			ret = error_errno(_("unable to write alternates file"));
+			goto out;
+		}
+	}
+
+	/* Reprepare the object database to activate alternates. */
+	odb_reprepare(source->odb);
+
+	ret = 0;
+
+out:
+	if (f)
+		fclose(f);
+	strbuf_release(&line);
 	strbuf_release(&path);
-	return 0;
+	strset_clear(&seen);
+	return ret;
 }
 
 static void odb_source_files_prepare(struct odb_source *source,
diff --git a/odb/source.h b/odb/source.h
index ea8675247e..86b82fab3b 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -5,6 +5,7 @@
 #include "object.h"
 #include "odb.h"
 #include "odb/transaction.h"
+#include "strvec.h"
 
 enum odb_source_type {
 	/*
@@ -26,6 +27,15 @@ enum odb_source_type {
 	ODB_SOURCE_INMEMORY,
 };
 
+struct odb_create_on_disk_options {
+	/*
+	 * Alternates that shall be written into the newly created object
+	 * database. Whether or not this option can be handled is specific to
+	 * the backend.
+	 */
+	const struct strvec *alternates;
+};
+
 /*
  * Convert between the enum and its name. Returns the equivalent of "unknown"
  * for unknown types.
@@ -106,7 +116,8 @@ struct odb_source {
 	 * This callback may be NULL in case the source does not need any
 	 * on-disk setup.
 	 */
-	int (*create_on_disk)(struct odb_source *source);
+	int (*create_on_disk)(struct odb_source *source,
+			      const struct odb_create_on_disk_options *opts);
 
 	/*
 	 * This callback is expected to prepare the source so that it becomes
@@ -356,11 +367,12 @@ static inline void odb_source_close(struct odb_source *source)
  * Create on-disk data structures that are required for this source to operate
  * correctly. Returns 0 on success, a negative error code otherwise.
  */
-static inline int odb_source_create_on_disk(struct odb_source *source)
+static inline int odb_source_create_on_disk(struct odb_source *source,
+					    const struct odb_create_on_disk_options *opts)
 {
 	if (!source->create_on_disk)
 		return 0;
-	return source->create_on_disk(source);
+	return source->create_on_disk(source, opts);
 }
 
 /*
diff --git a/setup.c b/setup.c
index e654e27d05..426cc7dff8 100644
--- a/setup.c
+++ b/setup.c
@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,
 
 void create_object_database(struct repository *repo)
 {
+	struct odb_create_on_disk_options opts = { 0 };
+
 	/*
 	 * Create the "objects" directory in the common directory. This is done
 	 * so that the repository can be discovered regardless of the backend
@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)
 
 	repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);
 
-	if (odb_source_create_on_disk(repo->objects->sources) < 0)
+	if (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)
 		die(_("failed creating object database"));
 }
 
-- 
2.55.0.822.g20453c30eb.dirty
Patrick SteinhardtAug 25, 2026, 14:11 UTC in reply to Patrick Steinhardt on lore

[PATCH 7/8] builtin/clone: write alternates via `odb_create_on_disk()`

When creating a repository with alternates we first initialize the object database and then write alternates to it in a separate step. This is unfortunate due to a couple of reasons:

  - It requires us to have a `write_alternates()` callback, which is
    unfortunate as we never even write alternates to an object database
    after it has been created.
  - We're about to make alternates an implementation detail of the
    object database's backend in a future patch series, so having this
    callback is suboptimal there.
  - The backend has more flexibility with how exactly alternates are
    configured when it itself is in full control over their setup at the
    time where it creates the object database itself.

We have thus introduced the ability to write alternates right at creation time in the preceding commits, and we have unified setup of alternates into a single location. All that's left to do for us now is to wire up alternates as an option for the database creation.

Do so.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 5 +----
 setup.c         | 9 ++++++---
 setup.h         | 2 +-
 3 files changed, 8 insertions(+), 8 deletions(-)
Show changes to 3 files +8 −8

builtin/clone.c, setup.c, setup.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 2e3473fddf..48ac379b1d 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1368,11 +1368,8 @@ int cmd_clone(int argc,
 	if (option_local > 0 && !is_local)
 		warning(_("--local is ignored"));
 
-	create_object_database(the_repository);
 	collect_alternates(&alternates, path, is_local);
-
-	for (size_t i = 0; i < alternates.nr; i++)
-		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
+	create_object_database(the_repository, &alternates);
 
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
diff --git a/setup.c b/setup.c
index 426cc7dff8..cfa286ff59 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,
 	return reinit;
 }
 
-void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo,
+			    const struct strvec *alternates)
 {
-	struct odb_create_on_disk_options opts = { 0 };
+	struct odb_create_on_disk_options opts = {
+		.alternates = alternates,
+	};
 
 	/*
 	 * Create the "objects" directory in the common directory. This is done
@@ -2907,7 +2910,7 @@ int init_db(struct repository *repo,
 	if (!(flags & INIT_DB_SKIP_REFDB))
 		create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
 	if (!(flags & INIT_DB_SKIP_ODB))
-		create_object_database(repo);
+		create_object_database(repo, NULL);
 
 	startup_info->have_repository = 1;
 
diff --git a/setup.h b/setup.h
index 570ebcd150..34e86dad37 100644
--- a/setup.h
+++ b/setup.h
@@ -277,7 +277,7 @@ void initialize_repository_version(struct repository *repo,
 				   enum ref_storage_format ref_storage_format,
 				   int reinit);
 void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
-void create_object_database(struct repository *repo);
+void create_object_database(struct repository *repo, const struct strvec *alternates);
 
 /*
  * NOTE NOTE NOTE!!
-- 
2.55.0.822.g20453c30eb.dirty
Patrick SteinhardtAug 25, 2026, 14:11 UTC in reply to Patrick Steinhardt on lore

[PATCH 8/8] odb/source: remove the ability to write alternates

There are no users of `odb_source_write_alternates()` in our tree anymore. Remove that function and its supporting infrastructure.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb.c                 |  9 ---------
 odb.h                 |  7 -------
 odb/source-files.c    | 55 ---------------------------------------------------
 odb/source-inmemory.c |  7 -------
 odb/source-loose.c    |  7 -------
 odb/source-packed.c   |  7 -------
 odb/source.h          | 26 ------------------------
 7 files changed, 118 deletions(-)
Show changes to 7 files +0 −118

odb.c, odb.h, odb/source-files.c, odb/source-inmemory.c, odb/source-loose.c, odb/source-packed.c, odb/source.h

diff --git a/odb.c b/odb.c
index 67d98d64fc..b531cf8fb3 100644
--- a/odb.c
+++ b/odb.c
@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
 	return alternate;
 }
 
-void odb_add_to_alternates_file(struct object_database *odb,
-				const char *dir)
-{
-	int ret = odb_source_write_alternate(odb->sources, dir);
-	if (ret < 0)
-		die(NULL);
-	odb_add_alternate_recursively(odb, dir, 0);
-}
-
 struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
 						const char *dir)
 {
diff --git a/odb.h b/odb.h
index b9e0db56ec..2d002461f8 100644
--- a/odb.h
+++ b/odb.h
@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,
  */
 int odb_has_alternates(struct object_database *odb);
 
-/*
- * Add the directory to the on-disk alternates file; the new entry will also
- * take effect in the current process.
- */
-void odb_add_to_alternates_file(struct object_database *odb,
-				const char *dir);
-
 /*
  * Add the directory to the in-memory list of alternate sources (along with any
  * recursive alternates it points to), but do not modify the on-disk alternates
diff --git a/odb/source-files.c b/odb/source-files.c
index 5e77b21d9f..feef9e169a 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -4,7 +4,6 @@
 #include "chdir-notify.h"
 #include "config.h"
 #include "gettext.h"
-#include "lockfile.h"
 #include "object-file.h"
 #include "odb.h"
 #include "odb/source.h"
@@ -303,59 +302,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,
 	return 0;
 }
 
-static int odb_source_files_write_alternate(struct odb_source *source,
-					    const char *alternate)
-{
-	struct lock_file lock = LOCK_INIT;
-	char *path = xstrfmt("%s/%s", source->path, "info/alternates");
-	FILE *in, *out;
-	int found = 0;
-	int ret;
-
-	repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
-				       LOCK_DIE_ON_ERROR);
-	out = fdopen_lock_file(&lock, "w");
-	if (!out) {
-		ret = error_errno(_("unable to fdopen alternates lockfile"));
-		goto out;
-	}
-
-	in = fopen(path, "r");
-	if (in) {
-		struct strbuf line = STRBUF_INIT;
-
-		while (strbuf_getline(&line, in) != EOF) {
-			if (!strcmp(alternate, line.buf)) {
-				found = 1;
-				break;
-			}
-			fprintf_or_die(out, "%s\n", line.buf);
-		}
-
-		strbuf_release(&line);
-		fclose(in);
-	} else if (errno != ENOENT) {
-		ret = error_errno(_("unable to read alternates file"));
-		goto out;
-	}
-
-	if (found) {
-		rollback_lock_file(&lock);
-	} else {
-		fprintf_or_die(out, "%s\n", alternate);
-		if (commit_lock_file(&lock)) {
-			ret = error_errno(_("unable to move new alternates file into place"));
-			goto out;
-		}
-	}
-
-	ret = 0;
-
-out:
-	free(path);
-	return ret;
-}
-
 static int too_many_loose_objects(struct odb_source_files *files, int limit)
 {
 	unsigned long loose_count;
@@ -839,7 +785,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
 	files->base.write_object_stream = odb_source_files_write_object_stream;
 	files->base.begin_transaction = odb_source_files_begin_transaction;
 	files->base.read_alternates = odb_source_files_read_alternates;
-	files->base.write_alternate = odb_source_files_write_alternate;
 	files->base.optimize = odb_source_files_optimize;
 	files->base.optimize_required = odb_source_files_optimize_required;
 
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
index 795672adf2..b00248dfb2 100644
--- a/odb/source-inmemory.c
+++ b/odb/source-inmemory.c
@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,
-					       const char *alternate UNUSED)
-{
-	return error("in-memory source does not support alternates");
-}
-
 static void odb_source_inmemory_close(struct odb_source *source UNUSED)
 {
 }
@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
 	source->base.freshen_object = odb_source_inmemory_freshen_object;
 	source->base.begin_transaction = odb_source_inmemory_begin_transaction;
 	source->base.read_alternates = odb_source_inmemory_read_alternates;
-	source->base.write_alternate = odb_source_inmemory_write_alternate;
 
 	return source;
 }
diff --git a/odb/source-loose.c b/odb/source-loose.c
index bb3455dfbd..0f9b30bac1 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,
-					    const char *alternate UNUSED)
-{
-	return error("loose source does not support alternates");
-}
-
 static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
 {
 	oidtree_clear(loose->cache);
@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
 	loose->base.write_object_stream = odb_source_loose_write_object_stream;
 	loose->base.begin_transaction = odb_source_loose_begin_transaction;
 	loose->base.read_alternates = odb_source_loose_read_alternates;
-	loose->base.write_alternate = odb_source_loose_write_alternate;
 
 	if (!is_absolute_path(loose->base.path))
 		chdir_notify_register(NULL, odb_source_loose_reparent, loose);
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 630d955585..c2d253759c 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,
-					     const char *alternate UNUSED)
-{
-	return error("packed backend cannot write alternates");
-}
-
 void (*report_garbage)(unsigned seen_bits, const char *path);
 
 static void report_helper(const struct string_list *list,
@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
 	packed->base.write_object_stream = odb_source_packed_write_object_stream;
 	packed->base.begin_transaction = odb_source_packed_begin_transaction;
 	packed->base.read_alternates = odb_source_packed_read_alternates;
-	packed->base.write_alternate = odb_source_packed_write_alternate;
 
 	if (!is_absolute_path(path))
 		chdir_notify_register(NULL, odb_source_packed_reparent, packed);
diff --git a/odb/source.h b/odb/source.h
index 86b82fab3b..5ee769e839 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -287,19 +287,6 @@ struct odb_source {
 	int (*read_alternates)(struct odb_source *source,
 			       struct strvec *out);
 
-	/*
-	 * This callback is expected to persist the singular alternate passed
-	 * to it into its list of alternates. Any pre-existing alternates are
-	 * expected to remain active. Subsequent calls to `read_alternates` are
-	 * thus expected to yield the pre-existing list of alternates plus the
-	 * newly added alternate appended to its end.
-	 *
-	 * The callback is expected to return 0 on success, a negative error
-	 * code otherwise.
-	 */
-	int (*write_alternate)(struct odb_source *source,
-			       const char *alternate);
-
 	/*
 	 * This callback is expected to optimize the object database source.
 	 * Returns 0 on success, a negative error code otherwise.
@@ -519,19 +506,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,
 	return source->read_alternates(source, out);
 }
 
-/*
- * Write and persist a new alternate object database source for the given
- * source. Any preexisting alternates are expected to stay valid, and the new
- * alternate shall be appended to the end of the list.
- *
- * Returns 0 on success, a negative error code otherwise.
- */
-static inline int odb_source_write_alternate(struct odb_source *source,
-					      const char *alternate)
-{
-	return source->write_alternate(source, alternate);
-}
-
 /*
  * Create a new transaction that can be used to write objects into a temporary
  * staging area. The objects will only be persisted when the transaction is
-- 
2.55.0.822.g20453c30eb.dirty
Toon ClaesAug 28, 2026, 14:52 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH 3/8] builtin/clone: refactor handling of "--reference{,-if-able}"

Patrick Steinhardt <ps@pks.im> writes:
Show 18 quoted lines
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 8c990ce0cc..8eae3ac7d9 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
>  	fclose(in);
>  }
>  
> -static void setup_reference(void)
> +static void collect_alternates(struct strvec *alternates)
>  {
> -	int required = 1;
> -	for_each_string_list(&option_required_reference,
> -			     add_one_reference, &required);
> -	required = 0;
> -	for_each_string_list(&option_optional_reference,
> -			     add_one_reference, &required);
> +	if (option_required_reference.nr || option_optional_reference.nr) {

I was about to ask why you're adding this guard, but it looks a lot more clean with the next commit on top.

Show 12 quoted lines
> +		struct add_one_alternate_data data = {
> +			.alternates = alternates,
> +			.required = 1,
> +		};
> +
> +		for_each_string_list(&option_required_reference,
> +				     add_one_alternate, &data);
> +		data.required = 0;
> +		for_each_string_list(&option_optional_reference,
> +				     add_one_alternate, &data);
> +	}
>  }
-- 
Laters,
Toon
Toon ClaesAug 28, 2026, 14:52 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH 4/8] builtin/clone: move setup of alternates for shared local clones

Patrick Steinhardt <ps@pks.im> writes:
Show 25 quoted lines
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 8eae3ac7d9..08c8f5a94f 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
>  	fclose(in);
>  }
>  
> -static void collect_alternates(struct strvec *alternates)
> +static void collect_alternates(struct strvec *alternates,
> +			       const char *src_repo, bool is_local)
>  {
>  	if (option_required_reference.nr || option_optional_reference.nr) {
>  		struct add_one_alternate_data data = {
> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
>  		for_each_string_list(&option_optional_reference,
>  				     add_one_alternate, &data);
>  	}
> +
> +	if (is_local) {
> +		struct strbuf commondir = STRBUF_INIT;
> +
> +		get_common_dir(&commondir, src_repo);
> +		if (option_shared)
> +			strvec_pushf(alternates, "%s/objects", commondir.buf);

Also the use of `commondir` seems to be only used if option_shared is set, but it will be used in an else in a subsequential commit.

> +
> +		strbuf_release(&commondir);
> +	}
>  }
-- 
Laters,
Toon
Toon ClaesAug 28, 2026, 14:52 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH 5/8] builtin/clone: move setup of alternates for non-shared local clones

Patrick Steinhardt <ps@pks.im> writes:
Show 79 quoted lines
> Similar as in the preceding commit, move the setup of alternates for
> local clones with "--no-shared" into `collect_alternates()`. With this
> step, the complete setup of alternates is now handled by that function.
>
> Note that besides moving stuff around, it also fixes a bug: previously,
> we did not know to resolve the referenced repository's common directory.
> Consequently, when referencing a worktree we failed to resolve
> alternates. But as `collect_alternates()` already knows to resolve the
> commondir for "--local" we can simply reuse this resolved path for our
> purpose.
>
> Add two tests, the first one of which exercises this bug to avoid future
> regressions. The second patch ensures that we properly handle relative
> alternates for a referenced worktree.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/clone.c            | 34 +++++++++++++++++++++++-----------
>  t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++
>  2 files changed, 48 insertions(+), 11 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 08c8f5a94f..2e3473fddf 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)
>  	return 0;
>  }
>  
> -static void copy_alternates(struct strbuf *src, const char *src_repo)
> +static void read_alternates(struct strvec *alternates, const char *src_repo)
>  {
>  	/*
>  	 * Read from the source objects/info/alternates file
> @@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
>  	 * to turn entries with paths relative to the original
>  	 * absolute, so that they can be used in the new repository.
>  	 */
> -	FILE *in = xfopen(src->buf, "r");
> +	FILE *in;
> +	struct strbuf path = STRBUF_INIT;
>  	struct strbuf line = STRBUF_INIT;
>  
> +	strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
> +
> +	in = fopen(path.buf, "r");
> +	if (!in) {
> +		if (errno == ENOENT)
> +			goto out;
> +		die_errno("could not read alternates file '%s'", path.buf);
> +	}
> +
>  	while (strbuf_getline(&line, in) != EOF) {
>  		char *abs_path;
>  		if (!line.len || line.buf[0] == '#')
>  			continue;
>  		if (is_absolute_path(line.buf)) {
> -			odb_add_to_alternates_file(the_repository->objects,
> -						   line.buf);
> +			strvec_push(alternates, line.buf);
>  			continue;
>  		}
>  		abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
>  		if (!normalize_path_copy(abs_path, abs_path))
> -			odb_add_to_alternates_file(the_repository->objects,
> -						   abs_path);
> +			strvec_push(alternates, abs_path);
>  		else
>  			warning("skipping invalid relative alternate: %s/%s",
>  				src_repo, line.buf);
>  		free(abs_path);
>  	}
> +
> +out:
> +	strbuf_release(&path);
>  	strbuf_release(&line);
> -	fclose(in);
> +	if (in)
> +		fclose(in);
Why not put this before the `out` label and remove the if?
>  }
-- 
Laters,
Toon
Toon ClaesAug 28, 2026, 14:53 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH 6/8] odb/source: support writing alternates when creating the database

Patrick Steinhardt <ps@pks.im> writes:
Show 20 quoted lines
> Add the ability to write alternates when creating the object database.
> This change allows us to remove the `write_alternates()` callback in a
> subsequent patch.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  odb/source-files.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
>  odb/source.h       | 18 +++++++++++---
>  setup.c            |  4 ++-
>  3 files changed, 89 insertions(+), 6 deletions(-)
>
> diff --git a/odb/source.h b/odb/source.h
> index ea8675247e..86b82fab3b 100644
> --- a/odb/source.h
> +++ b/odb/source.h
> @@ -5,6 +5,7 @@
>  #include "object.h"
>  #include "odb.h"
>  #include "odb/transaction.h"
> +#include "strvec.h"

I see `struct strvec;` is already defined below odb_source_type_to_name(). Can we not include this and put the new struct below the strvec definition?

-- 
Laters,
Toon
Toon ClaesAug 28, 2026, 14:53 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH 8/8] odb/source: remove the ability to write alternates

Patrick Steinhardt <ps@pks.im> writes:
Show 79 quoted lines
> There are no users of `odb_source_write_alternates()` in our tree
> anymore. Remove that function and its supporting infrastructure.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  odb.c                 |  9 ---------
>  odb.h                 |  7 -------
>  odb/source-files.c    | 55 ---------------------------------------------------
>  odb/source-inmemory.c |  7 -------
>  odb/source-loose.c    |  7 -------
>  odb/source-packed.c   |  7 -------
>  odb/source.h          | 26 ------------------------
>  7 files changed, 118 deletions(-)
>
> diff --git a/odb.c b/odb.c
> index 67d98d64fc..b531cf8fb3 100644
> --- a/odb.c
> +++ b/odb.c
> @@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
>  	return alternate;
>  }
>  
> -void odb_add_to_alternates_file(struct object_database *odb,
> -				const char *dir)
> -{
> -	int ret = odb_source_write_alternate(odb->sources, dir);
> -	if (ret < 0)
> -		die(NULL);
> -	odb_add_alternate_recursively(odb, dir, 0);
> -}
> -
>  struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
>  						const char *dir)
>  {
> diff --git a/odb.h b/odb.h
> index b9e0db56ec..2d002461f8 100644
> --- a/odb.h
> +++ b/odb.h
> @@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,
>   */
>  int odb_has_alternates(struct object_database *odb);
>  
> -/*
> - * Add the directory to the on-disk alternates file; the new entry will also
> - * take effect in the current process.
> - */
> -void odb_add_to_alternates_file(struct object_database *odb,
> -				const char *dir);
> -
>  /*
>   * Add the directory to the in-memory list of alternate sources (along with any
>   * recursive alternates it points to), but do not modify the on-disk alternates
> diff --git a/odb/source-files.c b/odb/source-files.c
> index 5e77b21d9f..feef9e169a 100644
> --- a/odb/source-files.c
> +++ b/odb/source-files.c
> @@ -4,7 +4,6 @@
>  #include "chdir-notify.h"
>  #include "config.h"
>  #include "gettext.h"
> -#include "lockfile.h"
>  #include "object-file.h"
>  #include "odb.h"
>  #include "odb/source.h"
> @@ -303,59 +302,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,
>  	return 0;
>  }
>  
> -static int odb_source_files_write_alternate(struct odb_source *source,
> -					    const char *alternate)
> -{
> -	struct lock_file lock = LOCK_INIT;
> -	char *path = xstrfmt("%s/%s", source->path, "info/alternates");
> -	FILE *in, *out;
> -	int found = 0;
> -	int ret;
> -
> -	repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
> -				       LOCK_DIE_ON_ERROR);
Why is the new implementation not using a lockfile?
-- 
Laters,
Toon
Junio C HamanoAug 28, 2026, 19:13 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH 6/8] odb/source: support writing alternates when creating the database

Patrick Steinhardt <ps@pks.im> writes:
Show 6 quoted lines
> Add the ability to write alternates when creating the object database.
> This change allows us to remove the `write_alternates()` callback in a
> subsequent patch.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
Show 36 quoted lines
> diff --git a/odb/source-files.c b/odb/source-files.c
> index b7b3a297bb..5e77b21d9f 100644
> --- a/odb/source-files.c
> +++ b/odb/source-files.c
> ...
> @@ -64,8 +70,71 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
> +	if (opts->alternates && opts->alternates->nr) {
> +		strbuf_reset(&path);
> +		strbuf_addf(&path, "%s/info/alternates", source->path);
> +
> +		/*
> +		 * The alternates file may already exist, e.g. when it has been
> +		 * seeded from a template directory. Read any preexisting
> +		 * entries so that we don't end up writing duplicates.
> +		 */
> +		f = fopen(path.buf, "r");
> +		if (f) {
> +			while (strbuf_getline(&line, f) != EOF)
> +				strset_add(&seen, line.buf);
> +
> +			if (ferror(f)) {
> +				ret = error_errno(_("unable to read alternates file"));
> +				goto out;
> +			}
> +
> +			fclose(f);
> +		} else if (errno != ENOENT) {
> +			ret = error_errno(_("unable to read alternates file"));
> +			goto out;
> +		}
> +
> +		f = fopen(path.buf, "a");
> +		if (!f) {
> +			ret = error_errno(_("unable to open alternates file for writing"));
> +			goto out;
> +		}

I understand that using 'a' instead of 'w' is an attempt to deal with the potential TOCTOU problem, but shouldn't we be using the standard lockfile API, which atomically adds (or fails to add) to avoid leaving a partially written file? Or does it not matter, since this is done only once upon repository creation when nobody should be looking at the files on the filesystem?

Thanks.
Patrick SteinhardtAug 31, 2026, 08:13 UTC in reply to Toon Claes on lore

Re: [PATCH 5/8] builtin/clone: move setup of alternates for non-shared local clones

On Fri, Aug 28, 2026 at 04:52:57PM +0200, Toon Claes wrote:
Show 52 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/builtin/clone.c b/builtin/clone.c
> > index 08c8f5a94f..2e3473fddf 100644
> > --- a/builtin/clone.c
> > +++ b/builtin/clone.c
> > @@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
> >  	 * to turn entries with paths relative to the original
> >  	 * absolute, so that they can be used in the new repository.
> >  	 */
> > -	FILE *in = xfopen(src->buf, "r");
> > +	FILE *in;
> > +	struct strbuf path = STRBUF_INIT;
> >  	struct strbuf line = STRBUF_INIT;
> >  
> > +	strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
> > +
> > +	in = fopen(path.buf, "r");
> > +	if (!in) {
> > +		if (errno == ENOENT)
> > +			goto out;
> > +		die_errno("could not read alternates file '%s'", path.buf);
> > +	}
> > +
> >  	while (strbuf_getline(&line, in) != EOF) {
> >  		char *abs_path;
> >  		if (!line.len || line.buf[0] == '#')
> >  			continue;
> >  		if (is_absolute_path(line.buf)) {
> > -			odb_add_to_alternates_file(the_repository->objects,
> > -						   line.buf);
> > +			strvec_push(alternates, line.buf);
> >  			continue;
> >  		}
> >  		abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
> >  		if (!normalize_path_copy(abs_path, abs_path))
> > -			odb_add_to_alternates_file(the_repository->objects,
> > -						   abs_path);
> > +			strvec_push(alternates, abs_path);
> >  		else
> >  			warning("skipping invalid relative alternate: %s/%s",
> >  				src_repo, line.buf);
> >  		free(abs_path);
> >  	}
> > +
> > +out:
> > +	strbuf_release(&path);
> >  	strbuf_release(&line);
> > -	fclose(in);
> > +	if (in)
> > +		fclose(in);
> 
> Why not put this before the `out` label and remove the if?

Mostly because it feels fragile to me. If we were to ever extend this function to have another `goto out` it's easy to miss that we don't close `in` anymore.

Patrick
Patrick SteinhardtAug 31, 2026, 08:14 UTC in reply to Toon Claes on lore

Re: [PATCH 6/8] odb/source: support writing alternates when creating the database

On Fri, Aug 28, 2026 at 04:53:30PM +0200, Toon Claes wrote:
Show 14 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/odb/source.h b/odb/source.h
> > index ea8675247e..86b82fab3b 100644
> > --- a/odb/source.h
> > +++ b/odb/source.h
> > @@ -5,6 +5,7 @@
> >  #include "object.h"
> >  #include "odb.h"
> >  #include "odb/transaction.h"
> > +#include "strvec.h"
> 
> I see `struct strvec;` is already defined below
> odb_source_type_to_name(). Can we not include this and put the new
> struct below the strvec definition?
Good point, will do.
Patrick
Patrick SteinhardtAug 31, 2026, 08:14 UTC in reply to Junio C Hamano on lore

Re: [PATCH 6/8] odb/source: support writing alternates when creating the database

On Fri, Aug 28, 2026 at 12:13:04PM -0700, Junio C Hamano wrote:
Show 52 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > Add the ability to write alternates when creating the object database.
> > This change allows us to remove the `write_alternates()` callback in a
> > subsequent patch.
> >
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > ---
> 
> > diff --git a/odb/source-files.c b/odb/source-files.c
> > index b7b3a297bb..5e77b21d9f 100644
> > --- a/odb/source-files.c
> > +++ b/odb/source-files.c
> > ...
> > @@ -64,8 +70,71 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
> > +	if (opts->alternates && opts->alternates->nr) {
> > +		strbuf_reset(&path);
> > +		strbuf_addf(&path, "%s/info/alternates", source->path);
> > +
> > +		/*
> > +		 * The alternates file may already exist, e.g. when it has been
> > +		 * seeded from a template directory. Read any preexisting
> > +		 * entries so that we don't end up writing duplicates.
> > +		 */
> > +		f = fopen(path.buf, "r");
> > +		if (f) {
> > +			while (strbuf_getline(&line, f) != EOF)
> > +				strset_add(&seen, line.buf);
> > +
> > +			if (ferror(f)) {
> > +				ret = error_errno(_("unable to read alternates file"));
> > +				goto out;
> > +			}
> > +
> > +			fclose(f);
> > +		} else if (errno != ENOENT) {
> > +			ret = error_errno(_("unable to read alternates file"));
> > +			goto out;
> > +		}
> > +
> > +		f = fopen(path.buf, "a");
> > +		if (!f) {
> > +			ret = error_errno(_("unable to open alternates file for writing"));
> > +			goto out;
> > +		}
> 
> I understand that using 'a' instead of 'w' is an attempt to deal
> with the potential TOCTOU problem, but shouldn't we be using the
> standard lockfile API, which atomically adds (or fails to add) to
> avoid leaving a partially written file?  Or does it not matter,
> since this is done only once upon repository creation when nobody
> should be looking at the files on the filesystem?

Toon also asked the same, and to be honest I never even considered doing this. It's the repository creation anyway, so it just didn't come to my mind that it could be racing. In theory though it can, even though it feels quite contrived.

In any case, I don't see a strong reason why we shouldn't use a lockfile here.

Patrick
Patrick SteinhardtAug 31, 2026, 08:14 UTC in reply to Toon Claes on lore

Re: [PATCH 8/8] odb/source: remove the ability to write alternates

On Fri, Aug 28, 2026 at 04:53:47PM +0200, Toon Claes wrote:
Show 22 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/odb/source-files.c b/odb/source-files.c
> > index 5e77b21d9f..feef9e169a 100644
> > --- a/odb/source-files.c
> > +++ b/odb/source-files.c
> > @@ -303,59 +302,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,
> >  	return 0;
> >  }
> >  
> > -static int odb_source_files_write_alternate(struct odb_source *source,
> > -					    const char *alternate)
> > -{
> > -	struct lock_file lock = LOCK_INIT;
> > -	char *path = xstrfmt("%s/%s", source->path, "info/alternates");
> > -	FILE *in, *out;
> > -	int found = 0;
> > -	int ret;
> > -
> > -	repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
> > -				       LOCK_DIE_ON_ERROR);
> 
> Why is the new implementation not using a lockfile?

Junio asked the same, and the only reason is that I simply didn't think about using a lockfile at all. Will fix, thanks.

Patrick
Patrick SteinhardtAug 31, 2026, 10:02 UTC in reply to Patrick Steinhardt on lore

[PATCH v2 0/8] odb: write alternates at creation time

Hi,

writing alternates into the object database currently happens via `odb_source_write_alternate()`. But while that creates the ability to create alternates at arbitrary points of a source's lifetime, we don't use that functionality in the first place. Instead, we only ever write alternates when creating a new repository.

This design is suboptimal due to a couple of reasons:
  - It requires us to have a `write_alternates()` callback, which is
    overblown as we never even write alternates to an object database
    after it has been created.
  - We're about to make alternates an implementation detail of the
    object database's backend in a future patch series, so alternate
    implementations may not even support them.
  - The backend has more flexibility with how exactly alternates are
    configured when it itself is in full control over their setup at the
    time where it creates the object database itself.

This patch series thus refactors how we handle alternates so that we don't write them ad-hoc anymore. Instead, the series introduces a new option for `odb_source_create_on_disk()` that makes it handle those alternates at creation time.

This is part of the bigger goal of moving handling of alternates into the "files" backend.

This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24) with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop `alternates_db` field, 2026-08-17) merged into it.

Changes in v2:
  - Use a lockfile to write "info/alternates" during creation time.
  - Remove useless "strvec.h" include by reordering declarations a bit.
  - Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im
Thanks!
Patrick
---
Patrick Steinhardt (8):
      builtin/clone: defer setup of the object database
      builtin/clone: move around `setup_reference()`
      builtin/clone: refactor handling of "--reference{,-if-able}"
      builtin/clone: move setup of alternates for shared local clones
      builtin/clone: move setup of alternates for non-shared local clones
      odb/source: support writing alternates when creating the database
      builtin/clone: write alternates via `odb_create_on_disk()`
      odb/source: remove the ability to write alternates
 builtin/clone.c            | 108 +++++++++++++++++++++++--------------
 odb.c                      |   9 ----
 odb.h                      |   7 ---
 odb/source-files.c         | 130 ++++++++++++++++++++++++++-------------------
 odb/source-inmemory.c      |   7 ---
 odb/source-loose.c         |   7 ---
 odb/source-packed.c        |   7 ---
 odb/source.h               |  43 +++++----------
 setup.c                    |  15 ++++--
 setup.h                    |  15 ++++--
 t/t5604-clone-reference.sh |  25 +++++++++
 11 files changed, 203 insertions(+), 170 deletions(-)
Range-diff versus v1:
1:  85b01b6b1b = 1:  ca4f283e20 builtin/clone: defer setup of the object database
2:  1e131d0a84 = 2:  4849b826ed builtin/clone: move around `setup_reference()`
3:  a6d33bf077 = 3:  870b994a9d builtin/clone: refactor handling of "--reference{,-if-able}"
4:  fd88a949f5 = 4:  6268e14ecb builtin/clone: move setup of alternates for shared local clones
5:  e18f135daa = 5:  c148c65508 builtin/clone: move setup of alternates for non-shared local clones
6:  e0733d5d63 ! 6:  360b8e83db odb/source: support writing alternates when creating the database
    @@ odb/source-files.c: static void odb_source_files_close(struct odb_source *source
     +static int odb_source_files_create_on_disk(struct odb_source *source,
     +					   const struct odb_create_on_disk_options *opts)
      {
    ++	struct lock_file alternates_lock = LOCK_INIT;
      	struct strbuf path = STRBUF_INIT;
     +	struct strset seen = STRSET_INIT;
     +	struct strbuf line = STRBUF_INIT;
    -+	FILE *f = NULL;
     +	int ret;
      
      	safe_create_dir(source->odb->repo, source->path, 1);
    @@ odb/source-files.c: static int odb_source_files_create_on_disk(struct odb_source
      	safe_create_dir(source->odb->repo, path.buf, 1);
      
     +	if (opts->alternates && opts->alternates->nr) {
    ++		FILE *alternates, *orig;
    ++
     +		strbuf_reset(&path);
     +		strbuf_addf(&path, "%s/info/alternates", source->path);
     +
    ++		repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,
    ++					       path.buf, LOCK_DIE_ON_ERROR);
    ++
    ++		alternates = fdopen_lock_file(&alternates_lock, "w");
    ++		if (!alternates) {
    ++			ret = error_errno(_("unable to fdopen alternates lockfile"));
    ++			goto out;
    ++		}
    ++
     +		/*
     +		 * The alternates file may already exist, e.g. when it has been
     +		 * seeded from a template directory. Read any preexisting
     +		 * entries so that we don't end up writing duplicates.
     +		 */
    -+		f = fopen(path.buf, "r");
    -+		if (f) {
    -+			while (strbuf_getline(&line, f) != EOF)
    ++		orig = fopen(path.buf, "r");
    ++		if (orig) {
    ++			while (strbuf_getline(&line, orig) != EOF) {
     +				strset_add(&seen, line.buf);
    ++				fprintf(alternates, "%s\n", line.buf);
    ++			}
     +
    -+			if (ferror(f)) {
    ++			if (ferror(orig)) {
     +				ret = error_errno(_("unable to read alternates file"));
    ++				fclose(orig);
     +				goto out;
     +			}
     +
    -+			fclose(f);
    ++			fclose(orig);
     +		} else if (errno != ENOENT) {
     +			ret = error_errno(_("unable to read alternates file"));
     +			goto out;
     +		}
     +
    -+		f = fopen(path.buf, "a");
    -+		if (!f) {
    -+			ret = error_errno(_("unable to open alternates file for writing"));
    -+			goto out;
    -+		}
    -+
     +		for (size_t i = 0; i < opts->alternates->nr; i++) {
     +			const char *alternate = opts->alternates->v[i];
    -+
     +			if (!strset_add(&seen, alternate))
     +				continue;
    -+
    -+			fprintf(f, "%s\n", alternate);
    ++			fprintf(alternates, "%s\n", alternate);
     +		}
     +
    -+		if (ferror(f)) {
    ++		if (ferror(alternates)) {
     +			ret = error_errno(_("unable to write alternates file"));
     +			goto out;
     +		}
     +
    -+		ret = fclose(f);
    -+		f = NULL;
    -+		if (ret) {
    -+			ret = error_errno(_("unable to write alternates file"));
    ++		if (commit_lock_file(&alternates_lock)) {
    ++			ret = error_errno(_("unable to commit alternates file"));
     +			goto out;
     +		}
     +	}
    @@ odb/source-files.c: static int odb_source_files_create_on_disk(struct odb_source
     +	ret = 0;
     +
     +out:
    -+	if (f)
    -+		fclose(f);
    ++	rollback_lock_file(&alternates_lock);
     +	strbuf_release(&line);
      	strbuf_release(&path);
     -	return 0;
    @@ odb/source-files.c: static int odb_source_files_create_on_disk(struct odb_source
      static void odb_source_files_prepare(struct odb_source *source,
     
      ## odb/source.h ##
    -@@
    - #include "object.h"
    - #include "odb.h"
    - #include "odb/transaction.h"
    -+#include "strvec.h"
    - 
    - enum odb_source_type {
    - 	/*
    -@@ odb/source.h: enum odb_source_type {
    - 	ODB_SOURCE_INMEMORY,
    - };
    +@@ odb/source.h: struct object_id;
    + struct odb_stream;
    + struct strvec;
      
     +struct odb_create_on_disk_options {
     +	/*
    @@ odb/source.h: enum odb_source_type {
     +};
     +
      /*
    -  * Convert between the enum and its name. Returns the equivalent of "unknown"
    -  * for unknown types.
    +  * The source is the part of the object database that stores the actual
    +  * objects. It thus encapsulates the logic to read and write the specific
     @@ odb/source.h: struct odb_source {
      	 * This callback may be NULL in case the source does not need any
      	 * on-disk setup.
7:  24ae5658ea = 7:  2809430e28 builtin/clone: write alternates via `odb_create_on_disk()`
8:  3bda635fb8 ! 8:  2abcd3b53c odb/source: remove the ability to write alternates
    @@ odb.h: int odb_mkstemp(struct object_database *odb,
       * recursive alternates it points to), but do not modify the on-disk alternates
     
      ## odb/source-files.c ##
    -@@
    - #include "chdir-notify.h"
    - #include "config.h"
    - #include "gettext.h"
    --#include "lockfile.h"
    - #include "object-file.h"
    - #include "odb.h"
    - #include "odb/source.h"
     @@ odb/source-files.c: static int odb_source_files_read_alternates(struct odb_source *source,
      	return 0;
      }

--- base-commit: afa255aeb620346d56a2c01fb5ae9163513c56d7 change-id: 20260813-pks-odb-write-alternates-at-creation-time-64010deb94a0

Patrick SteinhardtAug 31, 2026, 10:02 UTC in reply to Patrick Steinhardt on lore

[PATCH v2 1/8] builtin/clone: defer setup of the object database

When cloning a repository we defer initialization of the reference database. This is because we don't yet know all details required for us to initialize the refdb in the first place. Most importantly, what we are missing is information about the object hash.

We don't do the same thing for the object database yet, but here we essentially have the same problem. While the "files" database does not need any information about the object format at creation time, alternate backends are likely to require that information so that they can properly set up their data structures.

Besides this forward-looking future proofing though, we also have a second use case for deferring initialization of the object database, namely alternates. When initializing the object database we do not yet know whether we'll need alternates or not because this depends on the repository we're about to clone from. If it is a local repository and the user has passed "--refernce{,-if-able}", then we will end up writing alternates into the object database.

The ugly part though is that we cannot determine where the repository is getting cloned from before it has been initialized. While we of course already have access to the user-provided URI, that URI can be very well rewritten via "url.<base>.insteadOf". We can of course read the global- and system-level configuration to resolve it. But we explicitly resolve the URI a second time after we have initialized the repository because it can happen that we copy a ".git/config" over from our templates, and that file may cause us to rewrite the path.

In a subsequent commit though we'll start to write alternates as part of the repository initialization, so we'll need to have the URI properly resolved before we can initialize the object database. This is ugly, but as mentioned above it makes sense for us to defer its initialization anyway so that we also know about the object hash already.

Introduce a new flag that makes `init_db()` skip initializing the object database. Expose `create_object_database()` and make use of it after we have resolved the URI.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 12 ++++++++----
 setup.c         |  8 +++++---
 setup.h         | 15 ++++++++++-----
 3 files changed, 23 insertions(+), 12 deletions(-)
Show changes to 3 files +23 −12

builtin/clone.c, setup.c, setup.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 5b25cca510..0a67492ebd 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1184,11 +1184,14 @@ int cmd_clone(int argc,
 	 * database. We do not yet know about the object format of the
 	 * repository, and reference backends may persist that information into
 	 * their on-disk data structures.
+	 *
+	 * Furthermore, we skip initializing the object database so that we can
+	 * first resolve potential alternates before creating it.
 	 */
 	init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
 		GIT_HASH_UNKNOWN, ref_storage_format, NULL,
 		do_not_override_repo_unix_permissions,
-		INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
+		INIT_DB_QUIET | INIT_DB_SKIP_REFDB | INIT_DB_SKIP_ODB);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
@@ -1311,9 +1314,6 @@ int cmd_clone(int argc,
 		strbuf_reset(&key);
 	}
 
-	if (option_required_reference.nr || option_optional_reference.nr)
-		setup_reference();
-
 	remote = remote_get_early(remote_name);
 
 	if (!option_rev)
@@ -1342,6 +1342,10 @@ int cmd_clone(int argc,
 	if (option_local > 0 && !is_local)
 		warning(_("--local is ignored"));
 
+	create_object_database(the_repository);
+	if (option_required_reference.nr || option_optional_reference.nr)
+		setup_reference();
+
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
 	transport->family = family;
diff --git a/setup.c b/setup.c
index d90654f584..e654e27d05 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,
 	return reinit;
 }
 
-static void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo)
 {
 	/*
 	 * Create the "objects" directory in the common directory. This is done
@@ -2829,7 +2829,8 @@ int init_db(struct repository *repo,
 	    const char *template_dir, int hash,
 	    enum ref_storage_format ref_storage_format,
 	    const char *initial_branch,
-	    int init_shared_repository, unsigned int flags)
+	    int init_shared_repository,
+	    enum init_db_flags flags)
 {
 	int reinit;
 	int exist_ok = flags & INIT_DB_EXIST_OK;
@@ -2903,7 +2904,8 @@ int init_db(struct repository *repo,
 
 	if (!(flags & INIT_DB_SKIP_REFDB))
 		create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
-	create_object_database(repo);
+	if (!(flags & INIT_DB_SKIP_ODB))
+		create_object_database(repo);
 
 	startup_info->have_repository = 1;
 
diff --git a/setup.h b/setup.h
index 763fd384e8..570ebcd150 100644
--- a/setup.h
+++ b/setup.h
@@ -256,9 +256,12 @@ int apply_repository_format(struct repository *repo,
 
 const char *get_template_dir(const char *option_template);
 
-#define INIT_DB_QUIET      (1 << 0)
-#define INIT_DB_EXIST_OK   (1 << 1)
-#define INIT_DB_SKIP_REFDB (1 << 2)
+enum init_db_flags {
+	INIT_DB_QUIET = (1 << 0),
+	INIT_DB_EXIST_OK = (1 << 1),
+	INIT_DB_SKIP_REFDB = (1 << 2),
+	INIT_DB_SKIP_ODB = (1 << 3),
+};
 
 int init_db(struct repository *repo,
 	    const char *git_dir,
@@ -266,13 +269,15 @@ int init_db(struct repository *repo,
 	    const char *worktree,
 	    const char *template_dir, int hash_algo,
 	    enum ref_storage_format ref_storage_format,
-	    const char *initial_branch, int init_shared_repository,
-	    unsigned int flags);
+	    const char *initial_branch,
+	    int init_shared_repository,
+	    enum init_db_flags flags);
 void initialize_repository_version(struct repository *repo,
 				   int hash_algo,
 				   enum ref_storage_format ref_storage_format,
 				   int reinit);
 void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
+void create_object_database(struct repository *repo);
 
 /*
  * NOTE NOTE NOTE!!
-- 
2.55.0.979.g7e5102b832.dirty
Patrick SteinhardtAug 31, 2026, 10:02 UTC in reply to Patrick Steinhardt on lore

[PATCH v2 2/8] builtin/clone: move around `setup_reference()`

In a subsequent commit, `setup_reference()` will start to call `copy_alternates()`. Prepare for this by moving the function further down so that we can avoid adding a declaration.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)
Show changes to builtin/clone.c +10 −10
diff --git a/builtin/clone.c b/builtin/clone.c
index 0a67492ebd..8c990ce0cc 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
 	return 0;
 }
 
-static void setup_reference(void)
-{
-	int required = 1;
-	for_each_string_list(&option_required_reference,
-			     add_one_reference, &required);
-	required = 0;
-	for_each_string_list(&option_optional_reference,
-			     add_one_reference, &required);
-}
-
 static void copy_alternates(struct strbuf *src, const char *src_repo)
 {
 	/*
@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
+static void setup_reference(void)
+{
+	int required = 1;
+	for_each_string_list(&option_required_reference,
+			     add_one_reference, &required);
+	required = 0;
+	for_each_string_list(&option_optional_reference,
+			     add_one_reference, &required);
+}
+
 static void mkdir_if_missing(const char *pathname, mode_t mode)
 {
 	struct stat st;
-- 
2.55.0.979.g7e5102b832.dirty
Patrick SteinhardtAug 31, 2026, 10:02 UTC in reply to Patrick Steinhardt on lore

[PATCH v2 3/8] builtin/clone: refactor handling of "--reference{,-if-able}"

Users can pass "--reference{,-if-able}" to git-clone(1) to instruct it to set up alternates for the newly created repository. This allows it to reuse objects from the source repository so that in the best case we don't have to clone all objects over.

Those options are handled by the confusingly named `setup_reference()` function -- without the above context, one might rightfully believe that it was about refs, not about alternates. The function itself is rather simple: we loop through all provided alternate paths and then, if such an alternate is valid, we write it to the object database.

In subsequent commits we're about to consolidate the complete setup of alternates into this function, and furthermore we'll refactor the setup of the object database to handle doing this for us instead of writing the alterantes into it one by one.

Prepare for this refactoring by collecting the alternates into a strvec. Rename the function to `collect_alternates()` to clarify its scope.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------
 1 file changed, 28 insertions(+), 16 deletions(-)
Show changes to builtin/clone.c +28 −16
diff --git a/builtin/clone.c b/builtin/clone.c
index 8c990ce0cc..8eae3ac7d9 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
 	return canon;
 }
 
-static int add_one_reference(struct string_list_item *item, void *cb_data)
+struct add_one_alternate_data {
+	struct strvec *alternates;
+	int required;
+};
+
+static int add_one_alternate(struct string_list_item *item, void *cb_data)
 {
+	struct add_one_alternate_data *data = cb_data;
 	struct strbuf err = STRBUF_INIT;
-	int *required = cb_data;
 	char *ref_git = compute_alternate_path(item->string, &err);
 
 	if (!ref_git) {
-		if (*required)
+		if (data->required)
 			die("%s", err.buf);
 		else
 			fprintf(stderr,
 				_("info: Could not add alternate for '%s': %s\n"),
 				item->string, err.buf);
 	} else {
-		struct strbuf sb = STRBUF_INIT;
-		strbuf_addf(&sb, "%s/objects", ref_git);
-		odb_add_to_alternates_file(the_repository->objects, sb.buf);
-		strbuf_release(&sb);
+		strvec_pushf(data->alternates, "%s/objects", ref_git);
 	}
 
 	strbuf_release(&err);
@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
-static void setup_reference(void)
+static void collect_alternates(struct strvec *alternates)
 {
-	int required = 1;
-	for_each_string_list(&option_required_reference,
-			     add_one_reference, &required);
-	required = 0;
-	for_each_string_list(&option_optional_reference,
-			     add_one_reference, &required);
+	if (option_required_reference.nr || option_optional_reference.nr) {
+		struct add_one_alternate_data data = {
+			.alternates = alternates,
+			.required = 1,
+		};
+
+		for_each_string_list(&option_required_reference,
+				     add_one_alternate, &data);
+		data.required = 0;
+		for_each_string_list(&option_optional_reference,
+				     add_one_alternate, &data);
+	}
 }
 
 static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -999,6 +1007,7 @@ int cmd_clone(int argc,
 			   N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
 		OPT_END()
 	};
+	struct strvec alternates = STRVEC_INIT;
 
 	const char * const builtin_clone_usage[] = {
 		N_("git clone [<options>] [--] <repo> [<dir>]"),
@@ -1343,8 +1352,10 @@ int cmd_clone(int argc,
 		warning(_("--local is ignored"));
 
 	create_object_database(the_repository);
-	if (option_required_reference.nr || option_optional_reference.nr)
-		setup_reference();
+	collect_alternates(&alternates);
+
+	for (size_t i = 0; i < alternates.nr; i++)
+		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
 
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
@@ -1641,6 +1652,7 @@ int cmd_clone(int argc,
 	string_list_clear(&option_not, 0);
 	string_list_clear(&option_config, 0);
 	string_list_clear(&server_options, 0);
+	strvec_clear(&alternates);
 
 	free(remote_name);
 	strbuf_release(&reflog_msg);
-- 
2.55.0.979.g7e5102b832.dirty
Patrick SteinhardtAug 31, 2026, 10:02 UTC in reply to Patrick Steinhardt on lore

[PATCH v2 4/8] builtin/clone: move setup of alternates for shared local clones

When cloning a local repository with "--shared" we add that repository to the new repository's alternates. This is done in `clone_local()`, which is responsible for performing local clones.

Move the logic into `collect_alternates()` to unify our setup of alternates. Furthermore, this will allow us to set up alternates right at creation time of the object database.

Note that the logic for cloning a local repository with "--no-shared" is not yet part of `collect_alternates()`. This will be handled in the next commit.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)
Show changes to builtin/clone.c +14 −9
diff --git a/builtin/clone.c b/builtin/clone.c
index 8eae3ac7d9..08c8f5a94f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
-static void collect_alternates(struct strvec *alternates)
+static void collect_alternates(struct strvec *alternates,
+			       const char *src_repo, bool is_local)
 {
 	if (option_required_reference.nr || option_optional_reference.nr) {
 		struct add_one_alternate_data data = {
@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
 		for_each_string_list(&option_optional_reference,
 				     add_one_alternate, &data);
 	}
+
+	if (is_local) {
+		struct strbuf commondir = STRBUF_INIT;
+
+		get_common_dir(&commondir, src_repo);
+		if (option_shared)
+			strvec_pushf(alternates, "%s/objects", commondir.buf);
+
+		strbuf_release(&commondir);
+	}
 }
 
 static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 
 static void clone_local(const char *src_repo, const char *dest_repo)
 {
-	if (option_shared) {
-		struct strbuf alt = STRBUF_INIT;
-		get_common_dir(&alt, src_repo);
-		strbuf_addstr(&alt, "/objects");
-		odb_add_to_alternates_file(the_repository->objects, alt.buf);
-		strbuf_release(&alt);
-	} else {
+	if (!option_shared) {
 		struct strbuf src = STRBUF_INIT;
 		struct strbuf dest = STRBUF_INIT;
 		get_common_dir(&src, src_repo);
@@ -1352,7 +1357,7 @@ int cmd_clone(int argc,
 		warning(_("--local is ignored"));
 
 	create_object_database(the_repository);
-	collect_alternates(&alternates);
+	collect_alternates(&alternates, path, is_local);
 
 	for (size_t i = 0; i < alternates.nr; i++)
 		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
-- 
2.55.0.979.g7e5102b832.dirty
Patrick SteinhardtAug 31, 2026, 10:02 UTC in reply to Patrick Steinhardt on lore

[PATCH v2 5/8] builtin/clone: move setup of alternates for non-shared local clones

Similar as in the preceding commit, move the setup of alternates for local clones with "--no-shared" into `collect_alternates()`. With this step, the complete setup of alternates is now handled by that function.

Note that besides moving stuff around, it also fixes a bug: previously, we did not know to resolve the referenced repository's common directory. Consequently, when referencing a worktree we failed to resolve alternates. But as `collect_alternates()` already knows to resolve the commondir for "--local" we can simply reuse this resolved path for our purpose.

Add two tests, the first one of which exercises this bug to avoid future regressions. The second patch ensures that we properly handle relative alternates for a referenced worktree.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c            | 34 +++++++++++++++++++++++-----------
 t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++
 2 files changed, 48 insertions(+), 11 deletions(-)
Show changes to 2 files +48 −11

builtin/clone.c, t/t5604-clone-reference.sh

diff --git a/builtin/clone.c b/builtin/clone.c
index 08c8f5a94f..2e3473fddf 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)
 	return 0;
 }
 
-static void copy_alternates(struct strbuf *src, const char *src_repo)
+static void read_alternates(struct strvec *alternates, const char *src_repo)
 {
 	/*
 	 * Read from the source objects/info/alternates file
@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	 * to turn entries with paths relative to the original
 	 * absolute, so that they can be used in the new repository.
 	 */
-	FILE *in = xfopen(src->buf, "r");
+	FILE *in;
+	struct strbuf path = STRBUF_INIT;
 	struct strbuf line = STRBUF_INIT;
 
+	strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
+
+	in = fopen(path.buf, "r");
+	if (!in) {
+		if (errno == ENOENT)
+			goto out;
+		die_errno("could not read alternates file '%s'", path.buf);
+	}
+
 	while (strbuf_getline(&line, in) != EOF) {
 		char *abs_path;
 		if (!line.len || line.buf[0] == '#')
 			continue;
 		if (is_absolute_path(line.buf)) {
-			odb_add_to_alternates_file(the_repository->objects,
-						   line.buf);
+			strvec_push(alternates, line.buf);
 			continue;
 		}
 		abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
 		if (!normalize_path_copy(abs_path, abs_path))
-			odb_add_to_alternates_file(the_repository->objects,
-						   abs_path);
+			strvec_push(alternates, abs_path);
 		else
 			warning("skipping invalid relative alternate: %s/%s",
 				src_repo, line.buf);
 		free(abs_path);
 	}
+
+out:
+	strbuf_release(&path);
 	strbuf_release(&line);
-	fclose(in);
+	if (in)
+		fclose(in);
 }
 
 static void collect_alternates(struct strvec *alternates,
@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,
 		get_common_dir(&commondir, src_repo);
 		if (option_shared)
 			strvec_pushf(alternates, "%s/objects", commondir.buf);
+		else
+			read_alternates(alternates, commondir.buf);
 
 		strbuf_release(&commondir);
 	}
@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 			continue;
 		}
 
-		/* Files that cannot be copied bit-for-bit... */
-		if (!fspathcmp(iter->relative_path, "info/alternates")) {
-			copy_alternates(src, src_repo);
+		/* Alternates were already handled earlier. */
+		if (!fspathcmp(iter->relative_path, "info/alternates"))
 			continue;
-		}
 
 		if (unlink(dest->buf) && errno != ENOENT)
 			die_errno(_("failed to unlink '%s'"), dest->buf);
diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
index 39a0c318df..9e4b98fdb8 100755
--- a/t/t5604-clone-reference.sh
+++ b/t/t5604-clone-reference.sh
@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '
 	git clone --no-local --reference graph.git --dissociate orig clone
 '
 
+test_expect_success 'local clone from linked worktree carries over alternates' '
+	rm -fr base derived derived-wt dst expect &&
+	git init base &&
+	test_commit -C base one &&
+	git clone --shared base derived &&
+	git -C derived worktree add ../derived-wt &&
+	git clone derived-wt dst &&
+	echo "$(pwd)/base/.git/objects" >expect &&
+	test_cmp expect dst/.git/objects/info/alternates &&
+	git -C dst fsck
+'
+
+test_expect_success 'local clone from linked worktree resolves relative alternates' '
+	rm -fr base derived derived-wt dst expect &&
+	git init base &&
+	test_commit -C base one &&
+	git clone --shared base derived &&
+	echo "../../../base/.git/objects" >derived/.git/objects/info/alternates &&
+	git -C derived worktree add ../derived-wt &&
+	git clone derived-wt dst &&
+	echo "$(pwd)/base/.git/objects" >expect &&
+	test_cmp expect dst/.git/objects/info/alternates &&
+	git -C dst fsck
+'
+
 test_done
-- 
2.55.0.979.g7e5102b832.dirty
Patrick SteinhardtAug 31, 2026, 10:02 UTC in reply to Patrick Steinhardt on lore

[PATCH v2 6/8] odb/source: support writing alternates when creating the database

Add the ability to write alternates when creating the object database. This change allows us to remove the `write_alternates()` callback in a subsequent patch.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 odb/source.h       | 17 +++++++++---
 setup.c            |  4 ++-
 3 files changed, 91 insertions(+), 6 deletions(-)
Show changes to 3 files +91 −6

odb/source-files.c, odb/source.h, setup.c

diff --git a/odb/source-files.c b/odb/source-files.c
index b7b3a297bb..8fe65d91f8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -18,6 +18,7 @@
 #include "run-command.h"
 #include "strbuf.h"
 #include "string-list.h"
+#include "strmap.h"
 #include "strvec.h"
 #include "tree.h"
 #include "write-or-die.h"
@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)
 	odb_source_close(&files->packed->base);
 }
 
-static int odb_source_files_create_on_disk(struct odb_source *source)
+static int odb_source_files_create_on_disk(struct odb_source *source,
+					   const struct odb_create_on_disk_options *opts)
 {
+	struct lock_file alternates_lock = LOCK_INIT;
 	struct strbuf path = STRBUF_INIT;
+	struct strset seen = STRSET_INIT;
+	struct strbuf line = STRBUF_INIT;
+	int ret;
 
 	safe_create_dir(source->odb->repo, source->path, 1);
 
@@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
 	strbuf_addf(&path, "%s/info", source->path);
 	safe_create_dir(source->odb->repo, path.buf, 1);
 
+	if (opts->alternates && opts->alternates->nr) {
+		FILE *alternates, *orig;
+
+		strbuf_reset(&path);
+		strbuf_addf(&path, "%s/info/alternates", source->path);
+
+		repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,
+					       path.buf, LOCK_DIE_ON_ERROR);
+
+		alternates = fdopen_lock_file(&alternates_lock, "w");
+		if (!alternates) {
+			ret = error_errno(_("unable to fdopen alternates lockfile"));
+			goto out;
+		}
+
+		/*
+		 * The alternates file may already exist, e.g. when it has been
+		 * seeded from a template directory. Read any preexisting
+		 * entries so that we don't end up writing duplicates.
+		 */
+		orig = fopen(path.buf, "r");
+		if (orig) {
+			while (strbuf_getline(&line, orig) != EOF) {
+				strset_add(&seen, line.buf);
+				fprintf(alternates, "%s\n", line.buf);
+			}
+
+			if (ferror(orig)) {
+				ret = error_errno(_("unable to read alternates file"));
+				fclose(orig);
+				goto out;
+			}
+
+			fclose(orig);
+		} else if (errno != ENOENT) {
+			ret = error_errno(_("unable to read alternates file"));
+			goto out;
+		}
+
+		for (size_t i = 0; i < opts->alternates->nr; i++) {
+			const char *alternate = opts->alternates->v[i];
+			if (!strset_add(&seen, alternate))
+				continue;
+			fprintf(alternates, "%s\n", alternate);
+		}
+
+		if (ferror(alternates)) {
+			ret = error_errno(_("unable to write alternates file"));
+			goto out;
+		}
+
+		if (commit_lock_file(&alternates_lock)) {
+			ret = error_errno(_("unable to commit alternates file"));
+			goto out;
+		}
+	}
+
+	/* Reprepare the object database to activate alternates. */
+	odb_reprepare(source->odb);
+
+	ret = 0;
+
+out:
+	rollback_lock_file(&alternates_lock);
+	strbuf_release(&line);
 	strbuf_release(&path);
-	return 0;
+	strset_clear(&seen);
+	return ret;
 }
 
 static void odb_source_files_prepare(struct odb_source *source,
diff --git a/odb/source.h b/odb/source.h
index ea8675247e..63f1c0c531 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -36,6 +36,15 @@ struct object_id;
 struct odb_stream;
 struct strvec;
 
+struct odb_create_on_disk_options {
+	/*
+	 * Alternates that shall be written into the newly created object
+	 * database. Whether or not this option can be handled is specific to
+	 * the backend.
+	 */
+	const struct strvec *alternates;
+};
+
 /*
  * The source is the part of the object database that stores the actual
  * objects. It thus encapsulates the logic to read and write the specific
@@ -106,7 +115,8 @@ struct odb_source {
 	 * This callback may be NULL in case the source does not need any
 	 * on-disk setup.
 	 */
-	int (*create_on_disk)(struct odb_source *source);
+	int (*create_on_disk)(struct odb_source *source,
+			      const struct odb_create_on_disk_options *opts);
 
 	/*
 	 * This callback is expected to prepare the source so that it becomes
@@ -356,11 +366,12 @@ static inline void odb_source_close(struct odb_source *source)
  * Create on-disk data structures that are required for this source to operate
  * correctly. Returns 0 on success, a negative error code otherwise.
  */
-static inline int odb_source_create_on_disk(struct odb_source *source)
+static inline int odb_source_create_on_disk(struct odb_source *source,
+					    const struct odb_create_on_disk_options *opts)
 {
 	if (!source->create_on_disk)
 		return 0;
-	return source->create_on_disk(source);
+	return source->create_on_disk(source, opts);
 }
 
 /*
diff --git a/setup.c b/setup.c
index e654e27d05..426cc7dff8 100644
--- a/setup.c
+++ b/setup.c
@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,
 
 void create_object_database(struct repository *repo)
 {
+	struct odb_create_on_disk_options opts = { 0 };
+
 	/*
 	 * Create the "objects" directory in the common directory. This is done
 	 * so that the repository can be discovered regardless of the backend
@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)
 
 	repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);
 
-	if (odb_source_create_on_disk(repo->objects->sources) < 0)
+	if (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)
 		die(_("failed creating object database"));
 }
 
-- 
2.55.0.979.g7e5102b832.dirty
Patrick SteinhardtAug 31, 2026, 10:02 UTC in reply to Patrick Steinhardt on lore

[PATCH v2 7/8] builtin/clone: write alternates via `odb_create_on_disk()`

When creating a repository with alternates we first initialize the object database and then write alternates to it in a separate step. This is unfortunate due to a couple of reasons:

  - It requires us to have a `write_alternates()` callback, which is
    unfortunate as we never even write alternates to an object database
    after it has been created.
  - We're about to make alternates an implementation detail of the
    object database's backend in a future patch series, so having this
    callback is suboptimal there.
  - The backend has more flexibility with how exactly alternates are
    configured when it itself is in full control over their setup at the
    time where it creates the object database itself.

We have thus introduced the ability to write alternates right at creation time in the preceding commits, and we have unified setup of alternates into a single location. All that's left to do for us now is to wire up alternates as an option for the database creation.

Do so.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 5 +----
 setup.c         | 9 ++++++---
 setup.h         | 2 +-
 3 files changed, 8 insertions(+), 8 deletions(-)
Show changes to 3 files +8 −8

builtin/clone.c, setup.c, setup.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 2e3473fddf..48ac379b1d 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1368,11 +1368,8 @@ int cmd_clone(int argc,
 	if (option_local > 0 && !is_local)
 		warning(_("--local is ignored"));
 
-	create_object_database(the_repository);
 	collect_alternates(&alternates, path, is_local);
-
-	for (size_t i = 0; i < alternates.nr; i++)
-		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
+	create_object_database(the_repository, &alternates);
 
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
diff --git a/setup.c b/setup.c
index 426cc7dff8..cfa286ff59 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,
 	return reinit;
 }
 
-void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo,
+			    const struct strvec *alternates)
 {
-	struct odb_create_on_disk_options opts = { 0 };
+	struct odb_create_on_disk_options opts = {
+		.alternates = alternates,
+	};
 
 	/*
 	 * Create the "objects" directory in the common directory. This is done
@@ -2907,7 +2910,7 @@ int init_db(struct repository *repo,
 	if (!(flags & INIT_DB_SKIP_REFDB))
 		create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
 	if (!(flags & INIT_DB_SKIP_ODB))
-		create_object_database(repo);
+		create_object_database(repo, NULL);
 
 	startup_info->have_repository = 1;
 
diff --git a/setup.h b/setup.h
index 570ebcd150..34e86dad37 100644
--- a/setup.h
+++ b/setup.h
@@ -277,7 +277,7 @@ void initialize_repository_version(struct repository *repo,
 				   enum ref_storage_format ref_storage_format,
 				   int reinit);
 void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
-void create_object_database(struct repository *repo);
+void create_object_database(struct repository *repo, const struct strvec *alternates);
 
 /*
  * NOTE NOTE NOTE!!
-- 
2.55.0.979.g7e5102b832.dirty
Patrick SteinhardtAug 31, 2026, 10:02 UTC in reply to Patrick Steinhardt on lore

[PATCH v2 8/8] odb/source: remove the ability to write alternates

There are no users of `odb_source_write_alternates()` in our tree anymore. Remove that function and its supporting infrastructure.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb.c                 |  9 ---------
 odb.h                 |  7 -------
 odb/source-files.c    | 54 ---------------------------------------------------
 odb/source-inmemory.c |  7 -------
 odb/source-loose.c    |  7 -------
 odb/source-packed.c   |  7 -------
 odb/source.h          | 26 -------------------------
 7 files changed, 117 deletions(-)
Show changes to 7 files +0 −117

odb.c, odb.h, odb/source-files.c, odb/source-inmemory.c, odb/source-loose.c, odb/source-packed.c, odb/source.h

diff --git a/odb.c b/odb.c
index 67d98d64fc..b531cf8fb3 100644
--- a/odb.c
+++ b/odb.c
@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
 	return alternate;
 }
 
-void odb_add_to_alternates_file(struct object_database *odb,
-				const char *dir)
-{
-	int ret = odb_source_write_alternate(odb->sources, dir);
-	if (ret < 0)
-		die(NULL);
-	odb_add_alternate_recursively(odb, dir, 0);
-}
-
 struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
 						const char *dir)
 {
diff --git a/odb.h b/odb.h
index b9e0db56ec..2d002461f8 100644
--- a/odb.h
+++ b/odb.h
@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,
  */
 int odb_has_alternates(struct object_database *odb);
 
-/*
- * Add the directory to the on-disk alternates file; the new entry will also
- * take effect in the current process.
- */
-void odb_add_to_alternates_file(struct object_database *odb,
-				const char *dir);
-
 /*
  * Add the directory to the in-memory list of alternate sources (along with any
  * recursive alternates it points to), but do not modify the on-disk alternates
diff --git a/odb/source-files.c b/odb/source-files.c
index 8fe65d91f8..b3f340dff8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -306,59 +306,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,
 	return 0;
 }
 
-static int odb_source_files_write_alternate(struct odb_source *source,
-					    const char *alternate)
-{
-	struct lock_file lock = LOCK_INIT;
-	char *path = xstrfmt("%s/%s", source->path, "info/alternates");
-	FILE *in, *out;
-	int found = 0;
-	int ret;
-
-	repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
-				       LOCK_DIE_ON_ERROR);
-	out = fdopen_lock_file(&lock, "w");
-	if (!out) {
-		ret = error_errno(_("unable to fdopen alternates lockfile"));
-		goto out;
-	}
-
-	in = fopen(path, "r");
-	if (in) {
-		struct strbuf line = STRBUF_INIT;
-
-		while (strbuf_getline(&line, in) != EOF) {
-			if (!strcmp(alternate, line.buf)) {
-				found = 1;
-				break;
-			}
-			fprintf_or_die(out, "%s\n", line.buf);
-		}
-
-		strbuf_release(&line);
-		fclose(in);
-	} else if (errno != ENOENT) {
-		ret = error_errno(_("unable to read alternates file"));
-		goto out;
-	}
-
-	if (found) {
-		rollback_lock_file(&lock);
-	} else {
-		fprintf_or_die(out, "%s\n", alternate);
-		if (commit_lock_file(&lock)) {
-			ret = error_errno(_("unable to move new alternates file into place"));
-			goto out;
-		}
-	}
-
-	ret = 0;
-
-out:
-	free(path);
-	return ret;
-}
-
 static int too_many_loose_objects(struct odb_source_files *files, int limit)
 {
 	unsigned long loose_count;
@@ -842,7 +789,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
 	files->base.write_object_stream = odb_source_files_write_object_stream;
 	files->base.begin_transaction = odb_source_files_begin_transaction;
 	files->base.read_alternates = odb_source_files_read_alternates;
-	files->base.write_alternate = odb_source_files_write_alternate;
 	files->base.optimize = odb_source_files_optimize;
 	files->base.optimize_required = odb_source_files_optimize_required;
 
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
index 795672adf2..b00248dfb2 100644
--- a/odb/source-inmemory.c
+++ b/odb/source-inmemory.c
@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,
-					       const char *alternate UNUSED)
-{
-	return error("in-memory source does not support alternates");
-}
-
 static void odb_source_inmemory_close(struct odb_source *source UNUSED)
 {
 }
@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
 	source->base.freshen_object = odb_source_inmemory_freshen_object;
 	source->base.begin_transaction = odb_source_inmemory_begin_transaction;
 	source->base.read_alternates = odb_source_inmemory_read_alternates;
-	source->base.write_alternate = odb_source_inmemory_write_alternate;
 
 	return source;
 }
diff --git a/odb/source-loose.c b/odb/source-loose.c
index bb3455dfbd..0f9b30bac1 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,
-					    const char *alternate UNUSED)
-{
-	return error("loose source does not support alternates");
-}
-
 static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
 {
 	oidtree_clear(loose->cache);
@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
 	loose->base.write_object_stream = odb_source_loose_write_object_stream;
 	loose->base.begin_transaction = odb_source_loose_begin_transaction;
 	loose->base.read_alternates = odb_source_loose_read_alternates;
-	loose->base.write_alternate = odb_source_loose_write_alternate;
 
 	if (!is_absolute_path(loose->base.path))
 		chdir_notify_register(NULL, odb_source_loose_reparent, loose);
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 630d955585..c2d253759c 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,
-					     const char *alternate UNUSED)
-{
-	return error("packed backend cannot write alternates");
-}
-
 void (*report_garbage)(unsigned seen_bits, const char *path);
 
 static void report_helper(const struct string_list *list,
@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
 	packed->base.write_object_stream = odb_source_packed_write_object_stream;
 	packed->base.begin_transaction = odb_source_packed_begin_transaction;
 	packed->base.read_alternates = odb_source_packed_read_alternates;
-	packed->base.write_alternate = odb_source_packed_write_alternate;
 
 	if (!is_absolute_path(path))
 		chdir_notify_register(NULL, odb_source_packed_reparent, packed);
diff --git a/odb/source.h b/odb/source.h
index 63f1c0c531..693a9fc604 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -286,19 +286,6 @@ struct odb_source {
 	int (*read_alternates)(struct odb_source *source,
 			       struct strvec *out);
 
-	/*
-	 * This callback is expected to persist the singular alternate passed
-	 * to it into its list of alternates. Any pre-existing alternates are
-	 * expected to remain active. Subsequent calls to `read_alternates` are
-	 * thus expected to yield the pre-existing list of alternates plus the
-	 * newly added alternate appended to its end.
-	 *
-	 * The callback is expected to return 0 on success, a negative error
-	 * code otherwise.
-	 */
-	int (*write_alternate)(struct odb_source *source,
-			       const char *alternate);
-
 	/*
 	 * This callback is expected to optimize the object database source.
 	 * Returns 0 on success, a negative error code otherwise.
@@ -518,19 +505,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,
 	return source->read_alternates(source, out);
 }
 
-/*
- * Write and persist a new alternate object database source for the given
- * source. Any preexisting alternates are expected to stay valid, and the new
- * alternate shall be appended to the end of the list.
- *
- * Returns 0 on success, a negative error code otherwise.
- */
-static inline int odb_source_write_alternate(struct odb_source *source,
-					      const char *alternate)
-{
-	return source->write_alternate(source, alternate);
-}
-
 /*
  * Create a new transaction that can be used to write objects into a temporary
  * staging area. The objects will only be persisted when the transaction is
-- 
2.55.0.979.g7e5102b832.dirty
Toon ClaesSep 1, 2026, 06:09 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v2 0/8] odb: write alternates at creation time

Patrick Steinhardt <ps@pks.im> writes:
Show 38 quoted lines
> Hi,
>
> writing alternates into the object database currently happens via
> `odb_source_write_alternate()`. But while that creates the ability to
> create alternates at arbitrary points of a source's lifetime, we don't
> use that functionality in the first place. Instead, we only ever write
> alternates when creating a new repository.
>
> This design is suboptimal due to a couple of reasons:
>
>   - It requires us to have a `write_alternates()` callback, which is
>     overblown as we never even write alternates to an object database
>     after it has been created.
>
>   - We're about to make alternates an implementation detail of the
>     object database's backend in a future patch series, so alternate
>     implementations may not even support them.
>
>   - The backend has more flexibility with how exactly alternates are
>     configured when it itself is in full control over their setup at the
>     time where it creates the object database itself.
>
> This patch series thus refactors how we handle alternates so that we
> don't write them ad-hoc anymore. Instead, the series introduces a new
> option for `odb_source_create_on_disk()` that makes it handle those
> alternates at creation time.
>
> This is part of the bigger goal of moving handling of alternates into
> the "files" backend.
>
> This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)
> with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop
> `alternates_db` field, 2026-08-17) merged into it.
>
> Changes in v2:
>   - Use a lockfile to write "info/alternates" during creation time.
>   - Remove useless "strvec.h" include by reordering declarations a bit.
>   - Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im
This version looks good to me!
-- 
Laters,
Toon
Justin ToblerSep 6, 2026, 16:42 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v2 1/8] builtin/clone: defer setup of the object database

On 26/08/31 12:02PM, Patrick Steinhardt wrote:
Show 10 quoted lines
> When cloning a repository we defer initialization of the reference
> database. This is because we don't yet know all details required for us
> to initialize the refdb in the first place. Most importantly, what we
> are missing is information about the object hash.
> 
> We don't do the same thing for the object database yet, but here we
> essentially have the same problem. While the "files" database does not
> need any information about the object format at creation time, alternate
> backends are likely to require that information so that they can
> properly set up their data structures.

Ok, so if we want to move the writing of alternates to happen at ODB creation time, this information will be needed ahead of time.

Show 22 quoted lines
> Besides this forward-looking future proofing though, we also have a
> second use case for deferring initialization of the object database,
> namely alternates. When initializing the object database we do not yet
> know whether we'll need alternates or not because this depends on the
> repository we're about to clone from. If it is a local repository and
> the user has passed "--refernce{,-if-able}", then we will end up writing
> alternates into the object database.
> 
> The ugly part though is that we cannot determine where the repository is
> getting cloned from before it has been initialized. While we of course
> already have access to the user-provided URI, that URI can be very well
> rewritten via "url.<base>.insteadOf". We can of course read the global-
> and system-level configuration to resolve it. But we explicitly resolve
> the URI a second time after we have initialized the repository because
> it can happen that we copy a ".git/config" over from our templates, and
> that file may cause us to rewrite the path.
> 
> In a subsequent commit though we'll start to write alternates as part of
> the repository initialization, so we'll need to have the URI properly
> resolved before we can initialize the object database. This is ugly, but
> as mentioned above it makes sense for us to defer its initialization
> anyway so that we also know about the object hash already.
Ok.
Show 28 quoted lines
> Introduce a new flag that makes `init_db()` skip initializing the object
> database. Expose `create_object_database()` and make use of it after we
> have resolved the URI.
> 
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/clone.c | 12 ++++++++----
>  setup.c         |  8 +++++---
>  setup.h         | 15 ++++++++++-----
>  3 files changed, 23 insertions(+), 12 deletions(-)
> 
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 5b25cca510..0a67492ebd 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -1184,11 +1184,14 @@ int cmd_clone(int argc,
>  	 * database. We do not yet know about the object format of the
>  	 * repository, and reference backends may persist that information into
>  	 * their on-disk data structures.
> +	 *
> +	 * Furthermore, we skip initializing the object database so that we can
> +	 * first resolve potential alternates before creating it.
>  	 */
>  	init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
>  		GIT_HASH_UNKNOWN, ref_storage_format, NULL,
>  		do_not_override_repo_unix_permissions,
> -		INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
> +		INIT_DB_QUIET | INIT_DB_SKIP_REFDB | INIT_DB_SKIP_ODB);

Ok, now we skip the initializing the ODB during init_db in favor of delaying it to after we have the required config info. This makes sense to me, but IMO the `init_db()` interface has grown quite awkward with these "skip" flags. It appears that there are only two callers of `init_db()` which makes me wonder if it would be simpler to just require them to explicitly set up the ref DB and ODB.

Show 17 quoted lines
>  	if (real_git_dir) {
>  		free((char *)git_dir);
> @@ -1311,9 +1314,6 @@ int cmd_clone(int argc,
>  		strbuf_reset(&key);
>  	}
>  
> -	if (option_required_reference.nr || option_optional_reference.nr)
> -		setup_reference();
> -
>  	remote = remote_get_early(remote_name);
>  
>  	if (!option_rev)
> @@ -1342,6 +1342,10 @@ int cmd_clone(int argc,
>  	if (option_local > 0 && !is_local)
>  		warning(_("--local is ignored"));
>  
> +	create_object_database(the_repository);
We now explicitly create the object database here.
> +	if (option_required_reference.nr || option_optional_reference.nr)
> +		setup_reference();
Any reason the reference setup is also further deferred here?
-Justin
Justin ToblerSep 6, 2026, 17:02 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v2 6/8] odb/source: support writing alternates when creating the database

On 26/08/31 12:02PM, Patrick Steinhardt wrote:
Show 8 quoted lines
> +struct odb_create_on_disk_options {
> +	/*
> +	 * Alternates that shall be written into the newly created object
> +	 * database. Whether or not this option can be handled is specific to
> +	 * the backend.
> +	 */
> +	const struct strvec *alternates;
> +};

So ODB interface here still exposes a way to signal alternates to the backend, but an alternative backend may not actually use alternates at all. Out of curiousity, does this mean git-clone(1) options such as "--shared" should only be allowed to work with the "files" backend? Or should the expected behavior of such options just be backend specific?

-Justin
Justin ToblerSep 6, 2026, 17:07 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v2 7/8] builtin/clone: write alternates via `odb_create_on_disk()`

On 26/08/31 12:02PM, Patrick Steinhardt wrote:
Show 7 quoted lines
> When creating a repository with alternates we first initialize the
> object database and then write alternates to it in a separate step. This
> is unfortunate due to a couple of reasons:
> 
>   - It requires us to have a `write_alternates()` callback, which is
>     unfortunate as we never even write alternates to an object database
>     after it has been created.
Happy to see the list of callbacks shrink. :)
Show 12 quoted lines
>   - We're about to make alternates an implementation detail of the
>     object database's backend in a future patch series, so having this
>     callback is suboptimal there.
> 
>   - The backend has more flexibility with how exactly alternates are
>     configured when it itself is in full control over their setup at the
>     time where it creates the object database itself.
> 
> We have thus introduced the ability to write alternates right at
> creation time in the preceding commits, and we have unified setup of
> alternates into a single location. All that's left to do for us now is
> to wire up alternates as an option for the database creation.
Makes sense.
Show 23 quoted lines
> Do so.
> 
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/clone.c | 5 +----
>  setup.c         | 9 ++++++---
>  setup.h         | 2 +-
>  3 files changed, 8 insertions(+), 8 deletions(-)
> 
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 2e3473fddf..48ac379b1d 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -1368,11 +1368,8 @@ int cmd_clone(int argc,
>  	if (option_local > 0 && !is_local)
>  		warning(_("--local is ignored"));
>  
> -	create_object_database(the_repository);
>  	collect_alternates(&alternates, path, is_local);
> -
> -	for (size_t i = 0; i < alternates.nr; i++)
> -		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
> +	create_object_database(the_repository, &alternates);

Alternates are no longer explicitly set up here and instead wired and handled via `create_object_database()`. This patch looks good.

-Justin
Patrick SteinhardtSep 7, 2026, 07:23 UTC in reply to Justin Tobler on lore

Re: [PATCH v2 1/8] builtin/clone: defer setup of the object database

On Sun, Sep 06, 2026 at 11:42:48AM -0500, Justin Tobler wrote:
Show 25 quoted lines
> On 26/08/31 12:02PM, Patrick Steinhardt wrote:
> > diff --git a/builtin/clone.c b/builtin/clone.c
> > index 5b25cca510..0a67492ebd 100644
> > --- a/builtin/clone.c
> > +++ b/builtin/clone.c
> > @@ -1184,11 +1184,14 @@ int cmd_clone(int argc,
> >  	 * database. We do not yet know about the object format of the
> >  	 * repository, and reference backends may persist that information into
> >  	 * their on-disk data structures.
> > +	 *
> > +	 * Furthermore, we skip initializing the object database so that we can
> > +	 * first resolve potential alternates before creating it.
> >  	 */
> >  	init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
> >  		GIT_HASH_UNKNOWN, ref_storage_format, NULL,
> >  		do_not_override_repo_unix_permissions,
> > -		INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
> > +		INIT_DB_QUIET | INIT_DB_SKIP_REFDB | INIT_DB_SKIP_ODB);
> 
> Ok, now we skip the initializing the ODB during init_db in favor of
> delaying it to after we have the required config info. This makes sense
> to me, but IMO the `init_db()` interface has grown quite awkward with
> these "skip" flags. It appears that there are only two callers of
> `init_db()` which makes me wonder if it would be simpler to just require
> them to explicitly set up the ref DB and ODB.

We can, but there are some nuances here that make this a bit more complicated. Most importantly, we'd start printing the message that the repository was (re)initialized _before_ we create the actual object and reference databases.

But thinking about this a bit more we can solve this, and we can even get rid of the flags completely here:

  - git-clone(1) always passes the QUIET flag, so we really only want to
    print the message for git-init(1) anyway. So we can lift the logic
    out of `init_db()` and then drop the flag.
  - Same for `EXIST_OK`, we never allow preexisting repositories when
    performing a clone.

The name `init_db()` would become very misleading in that case though, so we should probably rename it to e.g. `create_repository()`. But overall the change makes sense, as it moves the command-specific logic to the commands themselves instead of making use of flags to control it. I like it.

Show 22 quoted lines
> > @@ -1311,9 +1314,6 @@ int cmd_clone(int argc,
> >  		strbuf_reset(&key);
> >  	}
> >  
> > -	if (option_required_reference.nr || option_optional_reference.nr)
> > -		setup_reference();
> > -
> >  	remote = remote_get_early(remote_name);
> >  
> >  	if (!option_rev)
> > @@ -1342,6 +1342,10 @@ int cmd_clone(int argc,
> >  	if (option_local > 0 && !is_local)
> >  		warning(_("--local is ignored"));
> >  
> > +	create_object_database(the_repository);
> 
> We now explicitly create the object database here.
> 
> > +	if (option_required_reference.nr || option_optional_reference.nr)
> > +		setup_reference();
> 
> Any reason the reference setup is also further deferred here?

You might think that this has something to do with refs ("refs/*"), but that's not the case. This setup here sets up alternates, and we can only set those up after we have created the object database. I'll note this in the commit message as it's quite non-obvious.

Thanks!
Patrick
Patrick SteinhardtSep 7, 2026, 07:23 UTC in reply to Justin Tobler on lore

Re: [PATCH v2 6/8] odb/source: support writing alternates when creating the database

On Sun, Sep 06, 2026 at 12:02:57PM -0500, Justin Tobler wrote:
Show 15 quoted lines
> On 26/08/31 12:02PM, Patrick Steinhardt wrote:
> > +struct odb_create_on_disk_options {
> > +	/*
> > +	 * Alternates that shall be written into the newly created object
> > +	 * database. Whether or not this option can be handled is specific to
> > +	 * the backend.
> > +	 */
> > +	const struct strvec *alternates;
> > +};
> 
> So ODB interface here still exposes a way to signal alternates to the
> backend, but an alternative backend may not actually use alternates at
> all. Out of curiousity, does this mean git-clone(1) options such as
> "--shared" should only be allowed to work with the "files" backend? Or
> should the expected behavior of such options just be backend specific?

The latter. It's totally feasible that an alternative backend may support alternates, but it's also feasible that it doesn't.

Patrick
Patrick SteinhardtSep 7, 2026, 08:25 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 0/9] odb: write alternates at creation time

Hi,

writing alternates into the object database currently happens via `odb_source_write_alternate()`. But while that creates the ability to create alternates at arbitrary points of a source's lifetime, we don't use that functionality in the first place. Instead, we only ever write alternates when creating a new repository.

This design is suboptimal due to a couple of reasons:
  - It requires us to have a `write_alternates()` callback, which is
    overblown as we never even write alternates to an object database
    after it has been created.
  - We're about to make alternates an implementation detail of the
    object database's backend in a future patch series, so alternate
    implementations may not even support them.
  - The backend has more flexibility with how exactly alternates are
    configured when it itself is in full control over their setup at the
    time where it creates the object database itself.

This patch series thus refactors how we handle alternates so that we don't write them ad-hoc anymore. Instead, the series introduces a new option for `odb_source_create_on_disk()` that makes it handle those alternates at creation time.

This is part of the bigger goal of moving handling of alternates into the "files" backend.

This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24) with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop `alternates_db` field, 2026-08-17) merged into it.

Changes in v3:
  - Refactor `init_db()` to not create the reference and object database
    at all anymore. Instead, it's now called `create_repository()` and
    it is responsible for creating the initial repository skeleton,
    only. This allows us to get rid of the flags and overall makes the
    logic more straight-forward by moving the command-specific logic
    into the respective commands.
  - A couple of typo fixes.
  - Link to v2: https://patch.msgid.link/20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im
Changes in v2:
  - Use a lockfile to write "info/alternates" during creation time.
  - Remove useless "strvec.h" include by reordering declarations a bit.
  - Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im
Thanks!
Patrick
---
Patrick Steinhardt (9):
      setup: split up concerns of `init_db()`
      builtin/clone: defer setup of the object database
      builtin/clone: move around `setup_reference()`
      builtin/clone: refactor handling of "--reference{,-if-able}"
      builtin/clone: move setup of alternates for shared local clones
      builtin/clone: move setup of alternates for non-shared local clones
      odb/source: support writing alternates when creating the database
      builtin/clone: write alternates via `odb_create_on_disk()`
      odb/source: remove the ability to write alternates
 builtin/clone.c            | 111 +++++++++++++++++++++++---------------
 builtin/init-db.c          |  32 ++++++++---
 odb.c                      |   9 ----
 odb.h                      |   7 ---
 odb/source-files.c         | 130 ++++++++++++++++++++++++++-------------------
 odb/source-inmemory.c      |   7 ---
 odb/source-loose.c         |   7 ---
 odb/source-packed.c        |   7 ---
 odb/source.h               |  43 +++++----------
 setup.c                    |  61 ++++++++-------------
 setup.h                    |  22 ++++----
 t/t5604-clone-reference.sh |  25 +++++++++
 12 files changed, 239 insertions(+), 222 deletions(-)
Range-diff versus v2:
 1:  1a1580185a <  -:  ---------- builtin/clone: defer setup of the object database
 -:  ---------- >  1:  512e7d1ef2 setup: split up concerns of `init_db()`
 -:  ---------- >  2:  a8808bcacf builtin/clone: defer setup of the object database
 2:  7d0d4832ed =  3:  0193fb3ada builtin/clone: move around `setup_reference()`
 3:  d547e62b5b !  4:  3c53ad13f2 builtin/clone: refactor handling of "--reference{,-if-able}"
    @@ Commit message
         In subsequent commits we're about to consolidate the complete setup of
         alternates into this function, and furthermore we'll refactor the setup
         of the object database to handle doing this for us instead of writing
    -    the alterantes into it one by one.
    +    the alternates into it one by one.
     
         Prepare for this refactoring by collecting the alternates into a strvec.
         Rename the function to `collect_alternates()` to clarify its scope.
 4:  ceb90593c8 =  5:  4fbcab0026 builtin/clone: move setup of alternates for shared local clones
 5:  d9587585f3 !  6:  b658ca0799 builtin/clone: move setup of alternates for non-shared local clones
    @@ Commit message
         purpose.
     
         Add two tests, the first one of which exercises this bug to avoid future
    -    regressions. The second patch ensures that we properly handle relative
    +    regressions. The second test ensures that we properly handle relative
         alternates for a referenced worktree.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
 6:  7575379b17 =  7:  bec8115803 odb/source: support writing alternates when creating the database
 7:  c07fd20d85 !  8:  3deaa7ef64 builtin/clone: write alternates via `odb_create_on_disk()`
    @@ builtin/clone.c: int cmd_clone(int argc,
      	transport = transport_get(remote, path ? path : remote->url.v[0]);
      	transport_set_verbosity(transport, option_verbosity, option_progress);
     
    + ## builtin/init-db.c ##
    +@@ builtin/init-db.c: int cmd_init_db(int argc,
    + 			  template_dir, hash_algo, ref_storage_format,
    + 			  init_shared_repository, &reinit);
    + 	create_reference_database(the_repository, initial_branch, quiet);
    +-	create_object_database(the_repository);
    ++	create_object_database(the_repository, NULL);
    + 
    + 	if (!quiet) {
    + 		int len = strlen(git_dir);
    +
      ## setup.c ##
     @@ setup.c: static int create_default_files(struct repository *repo,
      	return reinit;
    @@ setup.c: static int create_default_files(struct repository *repo,
      
      	/*
      	 * Create the "objects" directory in the common directory. This is done
    -@@ setup.c: int init_db(struct repository *repo,
    - 	if (!(flags & INIT_DB_SKIP_REFDB))
    - 		create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
    - 	if (!(flags & INIT_DB_SKIP_ODB))
    --		create_object_database(repo);
    -+		create_object_database(repo, NULL);
    - 
    - 	startup_info->have_repository = 1;
    - 
     
      ## setup.h ##
     @@ setup.h: void initialize_repository_version(struct repository *repo,
 8:  a8b9f51897 =  9:  d751b886b3 odb/source: remove the ability to write alternates

--- base-commit: afa255aeb620346d56a2c01fb5ae9163513c56d7 change-id: 20260813-pks-odb-write-alternates-at-creation-time-64010deb94a0

Patrick SteinhardtSep 7, 2026, 08:25 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 1/9] setup: split up concerns of `init_db()`

The function `init_db()` is responsible for creating the on-disk directory structure required for a Git repository. It is used by both git-init(1) and git-clone(1), and because their expected behaviour is different we support a couple of flags:

  - The `QUIET` flag controls whether the command is quiet or not. For
    git-init(1) this is user-controllable, whereas for git-clone(1)
    we're always quiet.
  - The `EXIST_OK` flag controls whether a preexisting repository is
    okay or not. For git-init(1) it is, for git-clone(1) it's not.
  - The `SKIP_REFDB` flag controls whether the reference database should
    already be created or not. For git-init(1) we do, but for
    git-clone(1) we don't because it does not yet know about the default
    branch and about the remote object hash.

Furthermore, we're about to add another divergence in behaviour, where we have to also skip creation of the object database in git-clone(1). This is becoming quite cumbersome though.

Instead of introducing another flag, start to split up concerns of the function so that we never create the reference or object database. This becomes the responsibility of the caller, which is thus free to defer their creation to a later point in time. This lets us get rid of most of the divergent behaviour:

  - We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags
    anymore.
  - We don't need the `QUIET` flag anymore, as nothing prints output
    except for the final status message that tells the user that the
    repository has been (re)initialized. But as this message is specific
    to git-init(1), we can easily move it there.

The only piece of information we still have to convey is whether or not reinitialization of a preexisting repository is okay. This is handled by a new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay to reinitialize the repository. Furthermore, the pointer will be written to to indicate whether the repository was reinitialized or not, which we need in git-init(1) to print the correct initialization message.

With these refactorings, `init_db()` is named quite misleadingly though, as we don't create any of the reference or object databases anymore. Rename it to `create_repository()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c   |  9 +++++----
 builtin/init-db.c | 32 ++++++++++++++++++++++++--------
 setup.c           | 54 +++++++++++++++++-------------------------------------
 setup.h           | 22 ++++++++++------------
 4 files changed, 56 insertions(+), 61 deletions(-)
Show changes to 4 files +56 −61

builtin/clone.c, builtin/init-db.c, setup.c, setup.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 5b25cca510..904d2d859f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1185,10 +1185,10 @@ int cmd_clone(int argc,
 	 * repository, and reference backends may persist that information into
 	 * their on-disk data structures.
 	 */
-	init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
-		GIT_HASH_UNKNOWN, ref_storage_format, NULL,
-		do_not_override_repo_unix_permissions,
-		INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
+	create_repository(the_repository, git_dir, real_git_dir, work_tree,
+			  option_template, GIT_HASH_UNKNOWN, ref_storage_format,
+			  do_not_override_repo_unix_permissions, NULL);
+	create_object_database(the_repository);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
@@ -1445,6 +1445,7 @@ int cmd_clone(int argc,
 	initialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);
 	repo_set_hash_algo(the_repository, hash_algo);
 	create_reference_database(the_repository, NULL, 1);
+	startup_info->have_repository = 1;
 
 	/*
 	 * Before fetching from the remote, download and install bundle
diff --git a/builtin/init-db.c b/builtin/init-db.c
index e96b1283b7..f2c7e3be6d 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -80,7 +80,7 @@ int cmd_init_db(int argc,
 	char *work_tree = NULL;
 	const char *template_dir = NULL;
 	char *template_dir_to_free = NULL;
-	unsigned int flags = 0;
+	int quiet = 0;
 	int bare = startup_info->force_bare_repository ? 1 : -1;
 	const char *object_format = NULL;
 	const char *ref_format = NULL;
@@ -102,7 +102,7 @@ int cmd_init_db(int argc,
 			.flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
 			.callback = shared_callback
 		},
-		OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
+		OPT_BOOL('q', "quiet", &quiet, N_("be quiet")),
 		OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
 			   N_("separate git dir from working tree")),
 		OPT_STRING('b', "initial-branch", &initial_branch, N_("name"),
@@ -113,7 +113,7 @@ int cmd_init_db(int argc,
 			   N_("specify the reference format to use")),
 		OPT_END()
 	};
-	int ret;
+	int reinit;
 
 	argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
 
@@ -247,14 +247,30 @@ int cmd_init_db(int argc,
 		die(_("--separate-git-dir incompatible with bare repository"));
 	}
 
-	flags |= INIT_DB_EXIST_OK;
-	ret = init_db(the_repository, git_dir, real_git_dir, work_tree,
-		      template_dir, hash_algo, ref_storage_format, initial_branch,
-		      init_shared_repository, flags);
+	create_repository(the_repository, git_dir, real_git_dir, work_tree,
+			  template_dir, hash_algo, ref_storage_format,
+			  init_shared_repository, &reinit);
+	create_reference_database(the_repository, initial_branch, quiet);
+	create_object_database(the_repository);
+
+	if (!quiet) {
+		int len = strlen(git_dir);
+
+		if (reinit)
+			printf(repo_settings_get_shared_repository(the_repository)
+			       ? _("Reinitialized existing shared Git repository in %s%s\n")
+			       : _("Reinitialized existing Git repository in %s%s\n"),
+			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+		else
+			printf(repo_settings_get_shared_repository(the_repository)
+			       ? _("Initialized empty shared Git repository in %s%s\n")
+			       : _("Initialized empty Git repository in %s%s\n"),
+			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+	}
 
 	free(template_dir_to_free);
 	free(real_git_dir_to_free);
 	free(work_tree);
 	free(git_dir);
-	return ret;
+	return 0;
 }
diff --git a/setup.c b/setup.c
index d90654f584..8c7b97f92e 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,
 	return reinit;
 }
 
-static void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo)
 {
 	/*
 	 * Create the "objects" directory in the common directory. This is done
@@ -2822,17 +2822,17 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 	}
 }
 
-int init_db(struct repository *repo,
-	    const char *git_dir,
-	    const char *real_git_dir,
-	    const char *worktree,
-	    const char *template_dir, int hash,
-	    enum ref_storage_format ref_storage_format,
-	    const char *initial_branch,
-	    int init_shared_repository, unsigned int flags)
+void create_repository(struct repository *repo,
+		       const char *git_dir,
+		       const char *real_git_dir,
+		       const char *worktree,
+		       const char *template_dir,
+		       int hash,
+		       enum ref_storage_format ref_storage_format,
+		       int init_shared_repository,
+		       int *reinit_ok)
 {
-	int reinit;
-	int exist_ok = flags & INIT_DB_EXIST_OK;
+	int reinit_ignored;
 	char *original_git_dir = real_pathdup(git_dir, 1);
 	struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
 	struct strbuf err = STRBUF_INIT;
@@ -2840,10 +2840,10 @@ int init_db(struct repository *repo,
 	if (real_git_dir) {
 		struct stat st;
 
-		if (!exist_ok && !stat(git_dir, &st))
+		if (!reinit_ok && !stat(git_dir, &st))
 			die(_("%s already exists"), git_dir);
 
-		if (!exist_ok && !stat(real_git_dir, &st))
+		if (!reinit_ok && !stat(real_git_dir, &st))
 			die(_("%s already exists"), real_git_dir);
 
 		apply_and_export_relative_gitdir(repo, real_git_dir, 1);
@@ -2877,8 +2877,10 @@ int init_db(struct repository *repo,
 
 	safe_create_dir(repo, git_dir, 0);
 
-	reinit = create_default_files(repo, template_dir, original_git_dir,
-				      &repo_fmt, init_shared_repository);
+	if (!reinit_ok)
+		reinit_ok = &reinit_ignored;
+	*reinit_ok = create_default_files(repo, template_dir, original_git_dir,
+					  &repo_fmt, init_shared_repository);
 
 	if (repo_settings_get_shared_repository(repo)) {
 		char buf[10];
@@ -2901,29 +2903,7 @@ int init_db(struct repository *repo,
 		repo_config_set(repo, "receive.denyNonFastforwards", "true");
 	}
 
-	if (!(flags & INIT_DB_SKIP_REFDB))
-		create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
-	create_object_database(repo);
-
-	startup_info->have_repository = 1;
-
-	if (!(flags & INIT_DB_QUIET)) {
-		int len = strlen(git_dir);
-
-		if (reinit)
-			printf(repo_settings_get_shared_repository(repo)
-			       ? _("Reinitialized existing shared Git repository in %s%s\n")
-			       : _("Reinitialized existing Git repository in %s%s\n"),
-			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
-		else
-			printf(repo_settings_get_shared_repository(repo)
-			       ? _("Initialized empty shared Git repository in %s%s\n")
-			       : _("Initialized empty Git repository in %s%s\n"),
-			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
-	}
-
 	clear_repository_format(&repo_fmt);
 	strbuf_release(&err);
 	free(original_git_dir);
-	return 0;
 }
diff --git a/setup.h b/setup.h
index 763fd384e8..c4aa464caa 100644
--- a/setup.h
+++ b/setup.h
@@ -256,23 +256,21 @@ int apply_repository_format(struct repository *repo,
 
 const char *get_template_dir(const char *option_template);
 
-#define INIT_DB_QUIET      (1 << 0)
-#define INIT_DB_EXIST_OK   (1 << 1)
-#define INIT_DB_SKIP_REFDB (1 << 2)
-
-int init_db(struct repository *repo,
-	    const char *git_dir,
-	    const char *real_git_dir,
-	    const char *worktree,
-	    const char *template_dir, int hash_algo,
-	    enum ref_storage_format ref_storage_format,
-	    const char *initial_branch, int init_shared_repository,
-	    unsigned int flags);
+void create_repository(struct repository *repo,
+		       const char *git_dir,
+		       const char *real_git_dir,
+		       const char *worktree,
+		       const char *template_dir,
+		       int hash_algo,
+		       enum ref_storage_format ref_storage_format,
+		       int init_shared_repository,
+		       int *reinit_ok);
 void initialize_repository_version(struct repository *repo,
 				   int hash_algo,
 				   enum ref_storage_format ref_storage_format,
 				   int reinit);
 void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
+void create_object_database(struct repository *repo);
 
 /*
  * NOTE NOTE NOTE!!
-- 
2.55.0.1007.g17ff1f9808.dirty
Patrick SteinhardtSep 7, 2026, 08:25 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 2/9] builtin/clone: defer setup of the object database

When cloning a repository we defer initialization of the reference database. This is because we don't yet know all details required for us to initialize the refdb in the first place. Most importantly, what we are missing is information about the object hash.

We don't do the same thing for the object database yet, but here we essentially have the same problem. While the "files" database does not need any information about the object format at creation time, alternate backends are likely to require that information so that they can properly set up their data structures.

Besides this forward-looking future proofing though, we also have a second use case for deferring initialization of the object database, namely alternates. When initializing the object database we do not yet know whether we'll need alternates or not because this depends on the repository we're about to clone from. If it is a local repository and the user has passed "--refernce{,-if-able}", then we will end up writing alternates into the object database.

The ugly part though is that we cannot determine where the repository is getting cloned from before it has been initialized. While we of course already have access to the user-provided URI, that URI can be very well rewritten via "url.<base>.insteadOf". We can of course read the global- and system-level configuration to resolve it. But we explicitly resolve the URI a second time after we have initialized the repository because it can happen that we copy a ".git/config" over from our templates, and that file may cause us to rewrite the path.

In a subsequent commit though we'll start to write alternates as part of the repository initialization, so we'll need to have the URI properly resolved before we can initialize the object database. This is ugly, but as mentioned above it makes sense for us to defer its initialization anyway so that we also know about the object hash already.

Defer creation of the object database until after we have resolved the URI.

Note that this also requires us to defer the call to `setup_reference()` until after we have created the object database. While you might think that this function has something to do with references ("refs/*"), it is in fact responsible for setting up alternates. Consequently, we can only call it after we have created the object database already.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
Show changes to builtin/clone.c +4 −4
diff --git a/builtin/clone.c b/builtin/clone.c
index 904d2d859f..bdcbd7aa1b 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1188,7 +1188,6 @@ int cmd_clone(int argc,
 	create_repository(the_repository, git_dir, real_git_dir, work_tree,
 			  option_template, GIT_HASH_UNKNOWN, ref_storage_format,
 			  do_not_override_repo_unix_permissions, NULL);
-	create_object_database(the_repository);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
@@ -1311,9 +1310,6 @@ int cmd_clone(int argc,
 		strbuf_reset(&key);
 	}
 
-	if (option_required_reference.nr || option_optional_reference.nr)
-		setup_reference();
-
 	remote = remote_get_early(remote_name);
 
 	if (!option_rev)
@@ -1342,6 +1338,10 @@ int cmd_clone(int argc,
 	if (option_local > 0 && !is_local)
 		warning(_("--local is ignored"));
 
+	create_object_database(the_repository);
+	if (option_required_reference.nr || option_optional_reference.nr)
+		setup_reference();
+
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
 	transport->family = family;
-- 
2.55.0.1007.g17ff1f9808.dirty
Patrick SteinhardtSep 7, 2026, 08:25 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 3/9] builtin/clone: move around `setup_reference()`

In a subsequent commit, `setup_reference()` will start to call `copy_alternates()`. Prepare for this by moving the function further down so that we can avoid adding a declaration.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)
Show changes to builtin/clone.c +10 −10
diff --git a/builtin/clone.c b/builtin/clone.c
index bdcbd7aa1b..ac5843d7b9 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
 	return 0;
 }
 
-static void setup_reference(void)
-{
-	int required = 1;
-	for_each_string_list(&option_required_reference,
-			     add_one_reference, &required);
-	required = 0;
-	for_each_string_list(&option_optional_reference,
-			     add_one_reference, &required);
-}
-
 static void copy_alternates(struct strbuf *src, const char *src_repo)
 {
 	/*
@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
+static void setup_reference(void)
+{
+	int required = 1;
+	for_each_string_list(&option_required_reference,
+			     add_one_reference, &required);
+	required = 0;
+	for_each_string_list(&option_optional_reference,
+			     add_one_reference, &required);
+}
+
 static void mkdir_if_missing(const char *pathname, mode_t mode)
 {
 	struct stat st;
-- 
2.55.0.1007.g17ff1f9808.dirty
Patrick SteinhardtSep 7, 2026, 08:25 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 4/9] builtin/clone: refactor handling of "--reference{,-if-able}"

Users can pass "--reference{,-if-able}" to git-clone(1) to instruct it to set up alternates for the newly created repository. This allows it to reuse objects from the source repository so that in the best case we don't have to clone all objects over.

Those options are handled by the confusingly named `setup_reference()` function -- without the above context, one might rightfully believe that it was about refs, not about alternates. The function itself is rather simple: we loop through all provided alternate paths and then, if such an alternate is valid, we write it to the object database.

In subsequent commits we're about to consolidate the complete setup of alternates into this function, and furthermore we'll refactor the setup of the object database to handle doing this for us instead of writing the alternates into it one by one.

Prepare for this refactoring by collecting the alternates into a strvec. Rename the function to `collect_alternates()` to clarify its scope.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------
 1 file changed, 28 insertions(+), 16 deletions(-)
Show changes to builtin/clone.c +28 −16
diff --git a/builtin/clone.c b/builtin/clone.c
index ac5843d7b9..8786a49332 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
 	return canon;
 }
 
-static int add_one_reference(struct string_list_item *item, void *cb_data)
+struct add_one_alternate_data {
+	struct strvec *alternates;
+	int required;
+};
+
+static int add_one_alternate(struct string_list_item *item, void *cb_data)
 {
+	struct add_one_alternate_data *data = cb_data;
 	struct strbuf err = STRBUF_INIT;
-	int *required = cb_data;
 	char *ref_git = compute_alternate_path(item->string, &err);
 
 	if (!ref_git) {
-		if (*required)
+		if (data->required)
 			die("%s", err.buf);
 		else
 			fprintf(stderr,
 				_("info: Could not add alternate for '%s': %s\n"),
 				item->string, err.buf);
 	} else {
-		struct strbuf sb = STRBUF_INIT;
-		strbuf_addf(&sb, "%s/objects", ref_git);
-		odb_add_to_alternates_file(the_repository->objects, sb.buf);
-		strbuf_release(&sb);
+		strvec_pushf(data->alternates, "%s/objects", ref_git);
 	}
 
 	strbuf_release(&err);
@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
-static void setup_reference(void)
+static void collect_alternates(struct strvec *alternates)
 {
-	int required = 1;
-	for_each_string_list(&option_required_reference,
-			     add_one_reference, &required);
-	required = 0;
-	for_each_string_list(&option_optional_reference,
-			     add_one_reference, &required);
+	if (option_required_reference.nr || option_optional_reference.nr) {
+		struct add_one_alternate_data data = {
+			.alternates = alternates,
+			.required = 1,
+		};
+
+		for_each_string_list(&option_required_reference,
+				     add_one_alternate, &data);
+		data.required = 0;
+		for_each_string_list(&option_optional_reference,
+				     add_one_alternate, &data);
+	}
 }
 
 static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -999,6 +1007,7 @@ int cmd_clone(int argc,
 			   N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
 		OPT_END()
 	};
+	struct strvec alternates = STRVEC_INIT;
 
 	const char * const builtin_clone_usage[] = {
 		N_("git clone [<options>] [--] <repo> [<dir>]"),
@@ -1339,8 +1348,10 @@ int cmd_clone(int argc,
 		warning(_("--local is ignored"));
 
 	create_object_database(the_repository);
-	if (option_required_reference.nr || option_optional_reference.nr)
-		setup_reference();
+	collect_alternates(&alternates);
+
+	for (size_t i = 0; i < alternates.nr; i++)
+		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
 
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
@@ -1638,6 +1649,7 @@ int cmd_clone(int argc,
 	string_list_clear(&option_not, 0);
 	string_list_clear(&option_config, 0);
 	string_list_clear(&server_options, 0);
+	strvec_clear(&alternates);
 
 	free(remote_name);
 	strbuf_release(&reflog_msg);
-- 
2.55.0.1007.g17ff1f9808.dirty
Patrick SteinhardtSep 7, 2026, 08:25 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 5/9] builtin/clone: move setup of alternates for shared local clones

When cloning a local repository with "--shared" we add that repository to the new repository's alternates. This is done in `clone_local()`, which is responsible for performing local clones.

Move the logic into `collect_alternates()` to unify our setup of alternates. Furthermore, this will allow us to set up alternates right at creation time of the object database.

Note that the logic for cloning a local repository with "--no-shared" is not yet part of `collect_alternates()`. This will be handled in the next commit.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)
Show changes to builtin/clone.c +14 −9
diff --git a/builtin/clone.c b/builtin/clone.c
index 8786a49332..011fc867c8 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
-static void collect_alternates(struct strvec *alternates)
+static void collect_alternates(struct strvec *alternates,
+			       const char *src_repo, bool is_local)
 {
 	if (option_required_reference.nr || option_optional_reference.nr) {
 		struct add_one_alternate_data data = {
@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
 		for_each_string_list(&option_optional_reference,
 				     add_one_alternate, &data);
 	}
+
+	if (is_local) {
+		struct strbuf commondir = STRBUF_INIT;
+
+		get_common_dir(&commondir, src_repo);
+		if (option_shared)
+			strvec_pushf(alternates, "%s/objects", commondir.buf);
+
+		strbuf_release(&commondir);
+	}
 }
 
 static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 
 static void clone_local(const char *src_repo, const char *dest_repo)
 {
-	if (option_shared) {
-		struct strbuf alt = STRBUF_INIT;
-		get_common_dir(&alt, src_repo);
-		strbuf_addstr(&alt, "/objects");
-		odb_add_to_alternates_file(the_repository->objects, alt.buf);
-		strbuf_release(&alt);
-	} else {
+	if (!option_shared) {
 		struct strbuf src = STRBUF_INIT;
 		struct strbuf dest = STRBUF_INIT;
 		get_common_dir(&src, src_repo);
@@ -1348,7 +1353,7 @@ int cmd_clone(int argc,
 		warning(_("--local is ignored"));
 
 	create_object_database(the_repository);
-	collect_alternates(&alternates);
+	collect_alternates(&alternates, path, is_local);
 
 	for (size_t i = 0; i < alternates.nr; i++)
 		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
-- 
2.55.0.1007.g17ff1f9808.dirty
Patrick SteinhardtSep 7, 2026, 08:25 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 6/9] builtin/clone: move setup of alternates for non-shared local clones

Similar as in the preceding commit, move the setup of alternates for local clones with "--no-shared" into `collect_alternates()`. With this step, the complete setup of alternates is now handled by that function.

Note that besides moving stuff around, it also fixes a bug: previously, we did not know to resolve the referenced repository's common directory. Consequently, when referencing a worktree we failed to resolve alternates. But as `collect_alternates()` already knows to resolve the commondir for "--local" we can simply reuse this resolved path for our purpose.

Add two tests, the first one of which exercises this bug to avoid future regressions. The second test ensures that we properly handle relative alternates for a referenced worktree.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c            | 34 +++++++++++++++++++++++-----------
 t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++
 2 files changed, 48 insertions(+), 11 deletions(-)
Show changes to 2 files +48 −11

builtin/clone.c, t/t5604-clone-reference.sh

diff --git a/builtin/clone.c b/builtin/clone.c
index 011fc867c8..84c1317867 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)
 	return 0;
 }
 
-static void copy_alternates(struct strbuf *src, const char *src_repo)
+static void read_alternates(struct strvec *alternates, const char *src_repo)
 {
 	/*
 	 * Read from the source objects/info/alternates file
@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	 * to turn entries with paths relative to the original
 	 * absolute, so that they can be used in the new repository.
 	 */
-	FILE *in = xfopen(src->buf, "r");
+	FILE *in;
+	struct strbuf path = STRBUF_INIT;
 	struct strbuf line = STRBUF_INIT;
 
+	strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
+
+	in = fopen(path.buf, "r");
+	if (!in) {
+		if (errno == ENOENT)
+			goto out;
+		die_errno("could not read alternates file '%s'", path.buf);
+	}
+
 	while (strbuf_getline(&line, in) != EOF) {
 		char *abs_path;
 		if (!line.len || line.buf[0] == '#')
 			continue;
 		if (is_absolute_path(line.buf)) {
-			odb_add_to_alternates_file(the_repository->objects,
-						   line.buf);
+			strvec_push(alternates, line.buf);
 			continue;
 		}
 		abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
 		if (!normalize_path_copy(abs_path, abs_path))
-			odb_add_to_alternates_file(the_repository->objects,
-						   abs_path);
+			strvec_push(alternates, abs_path);
 		else
 			warning("skipping invalid relative alternate: %s/%s",
 				src_repo, line.buf);
 		free(abs_path);
 	}
+
+out:
+	strbuf_release(&path);
 	strbuf_release(&line);
-	fclose(in);
+	if (in)
+		fclose(in);
 }
 
 static void collect_alternates(struct strvec *alternates,
@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,
 		get_common_dir(&commondir, src_repo);
 		if (option_shared)
 			strvec_pushf(alternates, "%s/objects", commondir.buf);
+		else
+			read_alternates(alternates, commondir.buf);
 
 		strbuf_release(&commondir);
 	}
@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 			continue;
 		}
 
-		/* Files that cannot be copied bit-for-bit... */
-		if (!fspathcmp(iter->relative_path, "info/alternates")) {
-			copy_alternates(src, src_repo);
+		/* Alternates were already handled earlier. */
+		if (!fspathcmp(iter->relative_path, "info/alternates"))
 			continue;
-		}
 
 		if (unlink(dest->buf) && errno != ENOENT)
 			die_errno(_("failed to unlink '%s'"), dest->buf);
diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
index 39a0c318df..9e4b98fdb8 100755
--- a/t/t5604-clone-reference.sh
+++ b/t/t5604-clone-reference.sh
@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '
 	git clone --no-local --reference graph.git --dissociate orig clone
 '
 
+test_expect_success 'local clone from linked worktree carries over alternates' '
+	rm -fr base derived derived-wt dst expect &&
+	git init base &&
+	test_commit -C base one &&
+	git clone --shared base derived &&
+	git -C derived worktree add ../derived-wt &&
+	git clone derived-wt dst &&
+	echo "$(pwd)/base/.git/objects" >expect &&
+	test_cmp expect dst/.git/objects/info/alternates &&
+	git -C dst fsck
+'
+
+test_expect_success 'local clone from linked worktree resolves relative alternates' '
+	rm -fr base derived derived-wt dst expect &&
+	git init base &&
+	test_commit -C base one &&
+	git clone --shared base derived &&
+	echo "../../../base/.git/objects" >derived/.git/objects/info/alternates &&
+	git -C derived worktree add ../derived-wt &&
+	git clone derived-wt dst &&
+	echo "$(pwd)/base/.git/objects" >expect &&
+	test_cmp expect dst/.git/objects/info/alternates &&
+	git -C dst fsck
+'
+
 test_done
-- 
2.55.0.1007.g17ff1f9808.dirty
Patrick SteinhardtSep 7, 2026, 08:25 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 7/9] odb/source: support writing alternates when creating the database

Add the ability to write alternates when creating the object database. This change allows us to remove the `write_alternates()` callback in a subsequent patch.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 odb/source.h       | 17 +++++++++---
 setup.c            |  4 ++-
 3 files changed, 91 insertions(+), 6 deletions(-)
Show changes to 3 files +91 −6

odb/source-files.c, odb/source.h, setup.c

diff --git a/odb/source-files.c b/odb/source-files.c
index b7b3a297bb..8fe65d91f8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -18,6 +18,7 @@
 #include "run-command.h"
 #include "strbuf.h"
 #include "string-list.h"
+#include "strmap.h"
 #include "strvec.h"
 #include "tree.h"
 #include "write-or-die.h"
@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)
 	odb_source_close(&files->packed->base);
 }
 
-static int odb_source_files_create_on_disk(struct odb_source *source)
+static int odb_source_files_create_on_disk(struct odb_source *source,
+					   const struct odb_create_on_disk_options *opts)
 {
+	struct lock_file alternates_lock = LOCK_INIT;
 	struct strbuf path = STRBUF_INIT;
+	struct strset seen = STRSET_INIT;
+	struct strbuf line = STRBUF_INIT;
+	int ret;
 
 	safe_create_dir(source->odb->repo, source->path, 1);
 
@@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
 	strbuf_addf(&path, "%s/info", source->path);
 	safe_create_dir(source->odb->repo, path.buf, 1);
 
+	if (opts->alternates && opts->alternates->nr) {
+		FILE *alternates, *orig;
+
+		strbuf_reset(&path);
+		strbuf_addf(&path, "%s/info/alternates", source->path);
+
+		repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,
+					       path.buf, LOCK_DIE_ON_ERROR);
+
+		alternates = fdopen_lock_file(&alternates_lock, "w");
+		if (!alternates) {
+			ret = error_errno(_("unable to fdopen alternates lockfile"));
+			goto out;
+		}
+
+		/*
+		 * The alternates file may already exist, e.g. when it has been
+		 * seeded from a template directory. Read any preexisting
+		 * entries so that we don't end up writing duplicates.
+		 */
+		orig = fopen(path.buf, "r");
+		if (orig) {
+			while (strbuf_getline(&line, orig) != EOF) {
+				strset_add(&seen, line.buf);
+				fprintf(alternates, "%s\n", line.buf);
+			}
+
+			if (ferror(orig)) {
+				ret = error_errno(_("unable to read alternates file"));
+				fclose(orig);
+				goto out;
+			}
+
+			fclose(orig);
+		} else if (errno != ENOENT) {
+			ret = error_errno(_("unable to read alternates file"));
+			goto out;
+		}
+
+		for (size_t i = 0; i < opts->alternates->nr; i++) {
+			const char *alternate = opts->alternates->v[i];
+			if (!strset_add(&seen, alternate))
+				continue;
+			fprintf(alternates, "%s\n", alternate);
+		}
+
+		if (ferror(alternates)) {
+			ret = error_errno(_("unable to write alternates file"));
+			goto out;
+		}
+
+		if (commit_lock_file(&alternates_lock)) {
+			ret = error_errno(_("unable to commit alternates file"));
+			goto out;
+		}
+	}
+
+	/* Reprepare the object database to activate alternates. */
+	odb_reprepare(source->odb);
+
+	ret = 0;
+
+out:
+	rollback_lock_file(&alternates_lock);
+	strbuf_release(&line);
 	strbuf_release(&path);
-	return 0;
+	strset_clear(&seen);
+	return ret;
 }
 
 static void odb_source_files_prepare(struct odb_source *source,
diff --git a/odb/source.h b/odb/source.h
index ea8675247e..63f1c0c531 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -36,6 +36,15 @@ struct object_id;
 struct odb_stream;
 struct strvec;
 
+struct odb_create_on_disk_options {
+	/*
+	 * Alternates that shall be written into the newly created object
+	 * database. Whether or not this option can be handled is specific to
+	 * the backend.
+	 */
+	const struct strvec *alternates;
+};
+
 /*
  * The source is the part of the object database that stores the actual
  * objects. It thus encapsulates the logic to read and write the specific
@@ -106,7 +115,8 @@ struct odb_source {
 	 * This callback may be NULL in case the source does not need any
 	 * on-disk setup.
 	 */
-	int (*create_on_disk)(struct odb_source *source);
+	int (*create_on_disk)(struct odb_source *source,
+			      const struct odb_create_on_disk_options *opts);
 
 	/*
 	 * This callback is expected to prepare the source so that it becomes
@@ -356,11 +366,12 @@ static inline void odb_source_close(struct odb_source *source)
  * Create on-disk data structures that are required for this source to operate
  * correctly. Returns 0 on success, a negative error code otherwise.
  */
-static inline int odb_source_create_on_disk(struct odb_source *source)
+static inline int odb_source_create_on_disk(struct odb_source *source,
+					    const struct odb_create_on_disk_options *opts)
 {
 	if (!source->create_on_disk)
 		return 0;
-	return source->create_on_disk(source);
+	return source->create_on_disk(source, opts);
 }
 
 /*
diff --git a/setup.c b/setup.c
index 8c7b97f92e..37a8e6f124 100644
--- a/setup.c
+++ b/setup.c
@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,
 
 void create_object_database(struct repository *repo)
 {
+	struct odb_create_on_disk_options opts = { 0 };
+
 	/*
 	 * Create the "objects" directory in the common directory. This is done
 	 * so that the repository can be discovered regardless of the backend
@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)
 
 	repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);
 
-	if (odb_source_create_on_disk(repo->objects->sources) < 0)
+	if (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)
 		die(_("failed creating object database"));
 }
 
-- 
2.55.0.1007.g17ff1f9808.dirty
Patrick SteinhardtSep 7, 2026, 08:25 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 8/9] builtin/clone: write alternates via `odb_create_on_disk()`

When creating a repository with alternates we first initialize the object database and then write alternates to it in a separate step. This is unfortunate due to a couple of reasons:

  - It requires us to have a `write_alternates()` callback, which is
    unfortunate as we never even write alternates to an object database
    after it has been created.
  - We're about to make alternates an implementation detail of the
    object database's backend in a future patch series, so having this
    callback is suboptimal there.
  - The backend has more flexibility with how exactly alternates are
    configured when it itself is in full control over their setup at the
    time where it creates the object database itself.

We have thus introduced the ability to write alternates right at creation time in the preceding commits, and we have unified setup of alternates into a single location. All that's left to do for us now is to wire up alternates as an option for the database creation.

Do so.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c   | 5 +----
 builtin/init-db.c | 2 +-
 setup.c           | 7 +++++--
 setup.h           | 2 +-
 4 files changed, 8 insertions(+), 8 deletions(-)
Show changes to 4 files +8 −8

builtin/clone.c, builtin/init-db.c, setup.c, setup.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 84c1317867..9e84646845 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1364,11 +1364,8 @@ int cmd_clone(int argc,
 	if (option_local > 0 && !is_local)
 		warning(_("--local is ignored"));
 
-	create_object_database(the_repository);
 	collect_alternates(&alternates, path, is_local);
-
-	for (size_t i = 0; i < alternates.nr; i++)
-		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
+	create_object_database(the_repository, &alternates);
 
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index f2c7e3be6d..5c22eae2f3 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -251,7 +251,7 @@ int cmd_init_db(int argc,
 			  template_dir, hash_algo, ref_storage_format,
 			  init_shared_repository, &reinit);
 	create_reference_database(the_repository, initial_branch, quiet);
-	create_object_database(the_repository);
+	create_object_database(the_repository, NULL);
 
 	if (!quiet) {
 		int len = strlen(git_dir);
diff --git a/setup.c b/setup.c
index 37a8e6f124..17d0d25973 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,
 	return reinit;
 }
 
-void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo,
+			    const struct strvec *alternates)
 {
-	struct odb_create_on_disk_options opts = { 0 };
+	struct odb_create_on_disk_options opts = {
+		.alternates = alternates,
+	};
 
 	/*
 	 * Create the "objects" directory in the common directory. This is done
diff --git a/setup.h b/setup.h
index c4aa464caa..e9941c6875 100644
--- a/setup.h
+++ b/setup.h
@@ -270,7 +270,7 @@ void initialize_repository_version(struct repository *repo,
 				   enum ref_storage_format ref_storage_format,
 				   int reinit);
 void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
-void create_object_database(struct repository *repo);
+void create_object_database(struct repository *repo, const struct strvec *alternates);
 
 /*
  * NOTE NOTE NOTE!!
-- 
2.55.0.1007.g17ff1f9808.dirty
Patrick SteinhardtSep 7, 2026, 08:25 UTC in reply to Patrick Steinhardt on lore

[PATCH v3 9/9] odb/source: remove the ability to write alternates

There are no users of `odb_source_write_alternates()` in our tree anymore. Remove that function and its supporting infrastructure.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb.c                 |  9 ---------
 odb.h                 |  7 -------
 odb/source-files.c    | 54 ---------------------------------------------------
 odb/source-inmemory.c |  7 -------
 odb/source-loose.c    |  7 -------
 odb/source-packed.c   |  7 -------
 odb/source.h          | 26 -------------------------
 7 files changed, 117 deletions(-)
Show changes to 7 files +0 −117

odb.c, odb.h, odb/source-files.c, odb/source-inmemory.c, odb/source-loose.c, odb/source-packed.c, odb/source.h

diff --git a/odb.c b/odb.c
index 67d98d64fc..b531cf8fb3 100644
--- a/odb.c
+++ b/odb.c
@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
 	return alternate;
 }
 
-void odb_add_to_alternates_file(struct object_database *odb,
-				const char *dir)
-{
-	int ret = odb_source_write_alternate(odb->sources, dir);
-	if (ret < 0)
-		die(NULL);
-	odb_add_alternate_recursively(odb, dir, 0);
-}
-
 struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
 						const char *dir)
 {
diff --git a/odb.h b/odb.h
index b9e0db56ec..2d002461f8 100644
--- a/odb.h
+++ b/odb.h
@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,
  */
 int odb_has_alternates(struct object_database *odb);
 
-/*
- * Add the directory to the on-disk alternates file; the new entry will also
- * take effect in the current process.
- */
-void odb_add_to_alternates_file(struct object_database *odb,
-				const char *dir);
-
 /*
  * Add the directory to the in-memory list of alternate sources (along with any
  * recursive alternates it points to), but do not modify the on-disk alternates
diff --git a/odb/source-files.c b/odb/source-files.c
index 8fe65d91f8..b3f340dff8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -306,59 +306,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,
 	return 0;
 }
 
-static int odb_source_files_write_alternate(struct odb_source *source,
-					    const char *alternate)
-{
-	struct lock_file lock = LOCK_INIT;
-	char *path = xstrfmt("%s/%s", source->path, "info/alternates");
-	FILE *in, *out;
-	int found = 0;
-	int ret;
-
-	repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
-				       LOCK_DIE_ON_ERROR);
-	out = fdopen_lock_file(&lock, "w");
-	if (!out) {
-		ret = error_errno(_("unable to fdopen alternates lockfile"));
-		goto out;
-	}
-
-	in = fopen(path, "r");
-	if (in) {
-		struct strbuf line = STRBUF_INIT;
-
-		while (strbuf_getline(&line, in) != EOF) {
-			if (!strcmp(alternate, line.buf)) {
-				found = 1;
-				break;
-			}
-			fprintf_or_die(out, "%s\n", line.buf);
-		}
-
-		strbuf_release(&line);
-		fclose(in);
-	} else if (errno != ENOENT) {
-		ret = error_errno(_("unable to read alternates file"));
-		goto out;
-	}
-
-	if (found) {
-		rollback_lock_file(&lock);
-	} else {
-		fprintf_or_die(out, "%s\n", alternate);
-		if (commit_lock_file(&lock)) {
-			ret = error_errno(_("unable to move new alternates file into place"));
-			goto out;
-		}
-	}
-
-	ret = 0;
-
-out:
-	free(path);
-	return ret;
-}
-
 static int too_many_loose_objects(struct odb_source_files *files, int limit)
 {
 	unsigned long loose_count;
@@ -842,7 +789,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
 	files->base.write_object_stream = odb_source_files_write_object_stream;
 	files->base.begin_transaction = odb_source_files_begin_transaction;
 	files->base.read_alternates = odb_source_files_read_alternates;
-	files->base.write_alternate = odb_source_files_write_alternate;
 	files->base.optimize = odb_source_files_optimize;
 	files->base.optimize_required = odb_source_files_optimize_required;
 
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
index 795672adf2..b00248dfb2 100644
--- a/odb/source-inmemory.c
+++ b/odb/source-inmemory.c
@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,
-					       const char *alternate UNUSED)
-{
-	return error("in-memory source does not support alternates");
-}
-
 static void odb_source_inmemory_close(struct odb_source *source UNUSED)
 {
 }
@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
 	source->base.freshen_object = odb_source_inmemory_freshen_object;
 	source->base.begin_transaction = odb_source_inmemory_begin_transaction;
 	source->base.read_alternates = odb_source_inmemory_read_alternates;
-	source->base.write_alternate = odb_source_inmemory_write_alternate;
 
 	return source;
 }
diff --git a/odb/source-loose.c b/odb/source-loose.c
index bb3455dfbd..0f9b30bac1 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,
-					    const char *alternate UNUSED)
-{
-	return error("loose source does not support alternates");
-}
-
 static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
 {
 	oidtree_clear(loose->cache);
@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
 	loose->base.write_object_stream = odb_source_loose_write_object_stream;
 	loose->base.begin_transaction = odb_source_loose_begin_transaction;
 	loose->base.read_alternates = odb_source_loose_read_alternates;
-	loose->base.write_alternate = odb_source_loose_write_alternate;
 
 	if (!is_absolute_path(loose->base.path))
 		chdir_notify_register(NULL, odb_source_loose_reparent, loose);
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 630d955585..c2d253759c 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,
-					     const char *alternate UNUSED)
-{
-	return error("packed backend cannot write alternates");
-}
-
 void (*report_garbage)(unsigned seen_bits, const char *path);
 
 static void report_helper(const struct string_list *list,
@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
 	packed->base.write_object_stream = odb_source_packed_write_object_stream;
 	packed->base.begin_transaction = odb_source_packed_begin_transaction;
 	packed->base.read_alternates = odb_source_packed_read_alternates;
-	packed->base.write_alternate = odb_source_packed_write_alternate;
 
 	if (!is_absolute_path(path))
 		chdir_notify_register(NULL, odb_source_packed_reparent, packed);
diff --git a/odb/source.h b/odb/source.h
index 63f1c0c531..693a9fc604 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -286,19 +286,6 @@ struct odb_source {
 	int (*read_alternates)(struct odb_source *source,
 			       struct strvec *out);
 
-	/*
-	 * This callback is expected to persist the singular alternate passed
-	 * to it into its list of alternates. Any pre-existing alternates are
-	 * expected to remain active. Subsequent calls to `read_alternates` are
-	 * thus expected to yield the pre-existing list of alternates plus the
-	 * newly added alternate appended to its end.
-	 *
-	 * The callback is expected to return 0 on success, a negative error
-	 * code otherwise.
-	 */
-	int (*write_alternate)(struct odb_source *source,
-			       const char *alternate);
-
 	/*
 	 * This callback is expected to optimize the object database source.
 	 * Returns 0 on success, a negative error code otherwise.
@@ -518,19 +505,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,
 	return source->read_alternates(source, out);
 }
 
-/*
- * Write and persist a new alternate object database source for the given
- * source. Any preexisting alternates are expected to stay valid, and the new
- * alternate shall be appended to the end of the list.
- *
- * Returns 0 on success, a negative error code otherwise.
- */
-static inline int odb_source_write_alternate(struct odb_source *source,
-					      const char *alternate)
-{
-	return source->write_alternate(source, alternate);
-}
-
 /*
  * Create a new transaction that can be used to write objects into a temporary
  * staging area. The objects will only be persisted when the transaction is
-- 
2.55.0.1007.g17ff1f9808.dirty
Justin ToblerSep 8, 2026, 22:12 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v3 1/9] setup: split up concerns of `init_db()`

On 26/09/07 10:25AM, Patrick Steinhardt wrote:
Show 41 quoted lines
> The function `init_db()` is responsible for creating the on-disk
> directory structure required for a Git repository. It is used by both
> git-init(1) and git-clone(1), and because their expected behaviour is
> different we support a couple of flags:
> 
>   - The `QUIET` flag controls whether the command is quiet or not. For
>     git-init(1) this is user-controllable, whereas for git-clone(1)
>     we're always quiet.
> 
>   - The `EXIST_OK` flag controls whether a preexisting repository is
>     okay or not. For git-init(1) it is, for git-clone(1) it's not.
> 
>   - The `SKIP_REFDB` flag controls whether the reference database should
>     already be created or not. For git-init(1) we do, but for
>     git-clone(1) we don't because it does not yet know about the default
>     branch and about the remote object hash.
> 
> Furthermore, we're about to add another divergence in behaviour, where
> we have to also skip creation of the object database in git-clone(1).
> This is becoming quite cumbersome though.
> 
> Instead of introducing another flag, start to split up concerns of the
> function so that we never create the reference or object database. This
> becomes the responsibility of the caller, which is thus free to defer
> their creation to a later point in time. This lets us get rid of most of
> the divergent behaviour:
> 
>   - We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags
>     anymore.
> 
>   - We don't need the `QUIET` flag anymore, as nothing prints output
>     except for the final status message that tells the user that the
>     repository has been (re)initialized. But as this message is specific
>     to git-init(1), we can easily move it there.
> 
> The only piece of information we still have to convey is whether or not
> reinitialization of a preexisting repository is okay. This is handled by
> a new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay
> to reinitialize the repository. Furthermore, the pointer will be written
> to to indicate whether the repository was reinitialized or not, which we
> need in git-init(1) to print the correct initialization message.

I like the proposed direction here much more than the previous version. :)

Show 28 quoted lines
> With these refactorings, `init_db()` is named quite misleadingly though,
> as we don't create any of the reference or object databases anymore.
> Rename it to `create_repository()`.
> 
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/clone.c   |  9 +++++----
>  builtin/init-db.c | 32 ++++++++++++++++++++++++--------
>  setup.c           | 54 +++++++++++++++++-------------------------------------
>  setup.h           | 22 ++++++++++------------
>  4 files changed, 56 insertions(+), 61 deletions(-)
> 
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 5b25cca510..904d2d859f 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -1185,10 +1185,10 @@ int cmd_clone(int argc,
>  	 * repository, and reference backends may persist that information into
>  	 * their on-disk data structures.
>  	 */
> -	init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
> -		GIT_HASH_UNKNOWN, ref_storage_format, NULL,
> -		do_not_override_repo_unix_permissions,
> -		INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
> +	create_repository(the_repository, git_dir, real_git_dir, work_tree,
> +			  option_template, GIT_HASH_UNKNOWN, ref_storage_format,
> +			  do_not_override_repo_unix_permissions, NULL);
> +	create_object_database(the_repository);

The implicit creation of the ref DB was already skipped so we only need to explicitly add `create_object_database()`. Makes sense.

Show 7 quoted lines
>  	if (real_git_dir) {
>  		free((char *)git_dir);
> @@ -1445,6 +1445,7 @@ int cmd_clone(int argc,
>  	initialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);
>  	repo_set_hash_algo(the_repository, hash_algo);
>  	create_reference_database(the_repository, NULL, 1);
> +	startup_info->have_repository = 1;

Ok IIUC, we now have to explicitly set "have_repository" because we don't want to set it if the ODB has not been created.

Show 61 quoted lines
>  	/*
>  	 * Before fetching from the remote, download and install bundle
> diff --git a/builtin/init-db.c b/builtin/init-db.c
> index e96b1283b7..f2c7e3be6d 100644
> --- a/builtin/init-db.c
> +++ b/builtin/init-db.c
> @@ -80,7 +80,7 @@ int cmd_init_db(int argc,
>  	char *work_tree = NULL;
>  	const char *template_dir = NULL;
>  	char *template_dir_to_free = NULL;
> -	unsigned int flags = 0;
> +	int quiet = 0;
>  	int bare = startup_info->force_bare_repository ? 1 : -1;
>  	const char *object_format = NULL;
>  	const char *ref_format = NULL;
> @@ -102,7 +102,7 @@ int cmd_init_db(int argc,
>  			.flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
>  			.callback = shared_callback
>  		},
> -		OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
> +		OPT_BOOL('q', "quiet", &quiet, N_("be quiet")),
>  		OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
>  			   N_("separate git dir from working tree")),
>  		OPT_STRING('b', "initial-branch", &initial_branch, N_("name"),
> @@ -113,7 +113,7 @@ int cmd_init_db(int argc,
>  			   N_("specify the reference format to use")),
>  		OPT_END()
>  	};
> -	int ret;
> +	int reinit;
>  
>  	argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
>  
> @@ -247,14 +247,30 @@ int cmd_init_db(int argc,
>  		die(_("--separate-git-dir incompatible with bare repository"));
>  	}
>  
> -	flags |= INIT_DB_EXIST_OK;
> -	ret = init_db(the_repository, git_dir, real_git_dir, work_tree,
> -		      template_dir, hash_algo, ref_storage_format, initial_branch,
> -		      init_shared_repository, flags);
> +	create_repository(the_repository, git_dir, real_git_dir, work_tree,
> +			  template_dir, hash_algo, ref_storage_format,
> +			  init_shared_repository, &reinit);
> +	create_reference_database(the_repository, initial_branch, quiet);
> +	create_object_database(the_repository);
> +
> +	if (!quiet) {
> +		int len = strlen(git_dir);
> +
> +		if (reinit)
> +			printf(repo_settings_get_shared_repository(the_repository)
> +			       ? _("Reinitialized existing shared Git repository in %s%s\n")
> +			       : _("Reinitialized existing Git repository in %s%s\n"),
> +			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
> +		else
> +			printf(repo_settings_get_shared_repository(the_repository)
> +			       ? _("Initialized empty shared Git repository in %s%s\n")
> +			       : _("Initialized empty Git repository in %s%s\n"),
> +			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
> +	}

Previously all of this logic was specific to git-init(1), so now its moved out of the generic function accordingly to furthur simplify the interface. Nice.

[snip]
Show 29 quoted lines
> diff --git a/setup.h b/setup.h
> index 763fd384e8..c4aa464caa 100644
> --- a/setup.h
> +++ b/setup.h
> @@ -256,23 +256,21 @@ int apply_repository_format(struct repository *repo,
>  
>  const char *get_template_dir(const char *option_template);
>  
> -#define INIT_DB_QUIET      (1 << 0)
> -#define INIT_DB_EXIST_OK   (1 << 1)
> -#define INIT_DB_SKIP_REFDB (1 << 2)
> -
> -int init_db(struct repository *repo,
> -	    const char *git_dir,
> -	    const char *real_git_dir,
> -	    const char *worktree,
> -	    const char *template_dir, int hash_algo,
> -	    enum ref_storage_format ref_storage_format,
> -	    const char *initial_branch, int init_shared_repository,
> -	    unsigned int flags);
> +void create_repository(struct repository *repo,
> +		       const char *git_dir,
> +		       const char *real_git_dir,
> +		       const char *worktree,
> +		       const char *template_dir,
> +		       int hash_algo,
> +		       enum ref_storage_format ref_storage_format,
> +		       int init_shared_repository,
> +		       int *reinit_ok);

While we are here, it might be nice to document these functions a little bit. The NULL/non-NULL behavior of `reinit_ok` may not be entirely obvious to future readers.

Show 6 quoted lines
>  void initialize_repository_version(struct repository *repo,
>  				   int hash_algo,
>  				   enum ref_storage_format ref_storage_format,
>  				   int reinit);
>  void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
> +void create_object_database(struct repository *repo);

It might also be nice to document these functions to explain exactly what we are "creating".

-Justin
Justin ToblerSep 8, 2026, 22:20 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v3 2/9] builtin/clone: defer setup of the object database

On 26/09/07 10:25AM, Patrick Steinhardt wrote:
Show 42 quoted lines
> When cloning a repository we defer initialization of the reference
> database. This is because we don't yet know all details required for us
> to initialize the refdb in the first place. Most importantly, what we
> are missing is information about the object hash.
> 
> We don't do the same thing for the object database yet, but here we
> essentially have the same problem. While the "files" database does not
> need any information about the object format at creation time, alternate
> backends are likely to require that information so that they can
> properly set up their data structures.
> 
> Besides this forward-looking future proofing though, we also have a
> second use case for deferring initialization of the object database,
> namely alternates. When initializing the object database we do not yet
> know whether we'll need alternates or not because this depends on the
> repository we're about to clone from. If it is a local repository and
> the user has passed "--refernce{,-if-able}", then we will end up writing
> alternates into the object database.
> 
> The ugly part though is that we cannot determine where the repository is
> getting cloned from before it has been initialized. While we of course
> already have access to the user-provided URI, that URI can be very well
> rewritten via "url.<base>.insteadOf". We can of course read the global-
> and system-level configuration to resolve it. But we explicitly resolve
> the URI a second time after we have initialized the repository because
> it can happen that we copy a ".git/config" over from our templates, and
> that file may cause us to rewrite the path.
> 
> In a subsequent commit though we'll start to write alternates as part of
> the repository initialization, so we'll need to have the URI properly
> resolved before we can initialize the object database. This is ugly, but
> as mentioned above it makes sense for us to defer its initialization
> anyway so that we also know about the object hash already.
> 
> Defer creation of the object database until after we have resolved the
> URI.
> 
> Note that this also requires us to defer the call to `setup_reference()`
> until after we have created the object database. While you might think
> that this function has something to do with references ("refs/*"), it is
> in fact responsible for setting up alternates. Consequently, we can only
> call it after we have created the object database already.

Ah ok, so now that we are deferring ODB creation a bit further, we need to also move ref DB creation to afterwards too. Makes sense.

Show 34 quoted lines
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/clone.c | 8 ++++----
>  1 file changed, 4 insertions(+), 4 deletions(-)
> 
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 904d2d859f..bdcbd7aa1b 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -1188,7 +1188,6 @@ int cmd_clone(int argc,
>  	create_repository(the_repository, git_dir, real_git_dir, work_tree,
>  			  option_template, GIT_HASH_UNKNOWN, ref_storage_format,
>  			  do_not_override_repo_unix_permissions, NULL);
> -	create_object_database(the_repository);
>  
>  	if (real_git_dir) {
>  		free((char *)git_dir);
> @@ -1311,9 +1310,6 @@ int cmd_clone(int argc,
>  		strbuf_reset(&key);
>  	}
>  
> -	if (option_required_reference.nr || option_optional_reference.nr)
> -		setup_reference();
> -
>  	remote = remote_get_early(remote_name);
>  
>  	if (!option_rev)
> @@ -1342,6 +1338,10 @@ int cmd_clone(int argc,
>  	if (option_local > 0 && !is_local)
>  		warning(_("--local is ignored"));
>  
> +	create_object_database(the_repository);
> +	if (option_required_reference.nr || option_optional_reference.nr)
> +		setup_reference();

Ok so the move here is really just in preparation for the alternates setup being handled as part of ODB creation in a subsequent patch and purely just structural. Looks good.

-Justin
Justin ToblerSep 8, 2026, 22:43 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v3 5/9] builtin/clone: move setup of alternates for shared local clones

On 26/09/07 10:25AM, Patrick Steinhardt wrote:
Show 37 quoted lines
> When cloning a local repository with "--shared" we add that repository
> to the new repository's alternates. This is done in `clone_local()`,
> which is responsible for performing local clones.
> 
> Move the logic into `collect_alternates()` to unify our setup of
> alternates. Furthermore, this will allow us to set up alternates right
> at creation time of the object database.
> 
> Note that the logic for cloning a local repository with "--no-shared" is
> not yet part of `collect_alternates()`. This will be handled in the next
> commit.
> 
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/clone.c | 23 ++++++++++++++---------
>  1 file changed, 14 insertions(+), 9 deletions(-)
> 
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 8786a49332..011fc867c8 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
>  	fclose(in);
>  }
>  
> -static void collect_alternates(struct strvec *alternates)
> +static void collect_alternates(struct strvec *alternates,
> +			       const char *src_repo, bool is_local)
>  {
>  	if (option_required_reference.nr || option_optional_reference.nr) {
>  		struct add_one_alternate_data data = {
> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
>  		for_each_string_list(&option_optional_reference,
>  				     add_one_alternate, &data);
>  	}
> +
> +	if (is_local) {
Could this condtional instead be:
  if (is_local && option_shared)
Show 8 quoted lines
> +		struct strbuf commondir = STRBUF_INIT;
> +
> +		get_common_dir(&commondir, src_repo);
> +		if (option_shared)
> +			strvec_pushf(alternates, "%s/objects", commondir.buf);
> +
> +		strbuf_release(&commondir);
> +	}
-Justin
Justin ToblerSep 8, 2026, 22:48 UTC in reply to Justin Tobler on lore

Re: [PATCH v3 5/9] builtin/clone: move setup of alternates for shared local clones

On 26/09/08 05:43PM, Justin Tobler wrote:
Show 6 quoted lines
> On 26/09/07 10:25AM, Patrick Steinhardt wrote:
> > +	if (is_local) {
> 
> Could this condtional instead be:
> 
>   if (is_local && option_shared)
Ahh nevermind, in the next patch we handle `!option_shared`.
-Justin
Patrick SteinhardtSep 9, 2026, 05:48 UTC in reply to Justin Tobler on lore

Re: [PATCH v3 1/9] setup: split up concerns of `init_db()`

On Tue, Sep 08, 2026 at 05:12:21PM -0500, Justin Tobler wrote:
Show 44 quoted lines
> On 26/09/07 10:25AM, Patrick Steinhardt wrote:
> > diff --git a/setup.h b/setup.h
> > index 763fd384e8..c4aa464caa 100644
> > --- a/setup.h
> > +++ b/setup.h
> > @@ -256,23 +256,21 @@ int apply_repository_format(struct repository *repo,
> >  
> >  const char *get_template_dir(const char *option_template);
> >  
> > -#define INIT_DB_QUIET      (1 << 0)
> > -#define INIT_DB_EXIST_OK   (1 << 1)
> > -#define INIT_DB_SKIP_REFDB (1 << 2)
> > -
> > -int init_db(struct repository *repo,
> > -	    const char *git_dir,
> > -	    const char *real_git_dir,
> > -	    const char *worktree,
> > -	    const char *template_dir, int hash_algo,
> > -	    enum ref_storage_format ref_storage_format,
> > -	    const char *initial_branch, int init_shared_repository,
> > -	    unsigned int flags);
> > +void create_repository(struct repository *repo,
> > +		       const char *git_dir,
> > +		       const char *real_git_dir,
> > +		       const char *worktree,
> > +		       const char *template_dir,
> > +		       int hash_algo,
> > +		       enum ref_storage_format ref_storage_format,
> > +		       int init_shared_repository,
> > +		       int *reinit_ok);
> 
> While we are here, it might be nice to document these functions a little
> bit. The NULL/non-NULL behavior of `reinit_ok` may not be entirely
> obvious to future readers.
> 
> >  void initialize_repository_version(struct repository *repo,
> >  				   int hash_algo,
> >  				   enum ref_storage_format ref_storage_format,
> >  				   int reinit);
> >  void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
> > +void create_object_database(struct repository *repo);
> 
> It might also be nice to document these functions to explain exactly
> what we are "creating".
Yeah, fair. Will add some comments here.
Patrick
Patrick SteinhardtSep 9, 2026, 05:48 UTC in reply to Justin Tobler on lore

Re: [PATCH v3 2/9] builtin/clone: defer setup of the object database

On Tue, Sep 08, 2026 at 05:20:06PM -0500, Justin Tobler wrote:
Show 46 quoted lines
> On 26/09/07 10:25AM, Patrick Steinhardt wrote:
> > When cloning a repository we defer initialization of the reference
> > database. This is because we don't yet know all details required for us
> > to initialize the refdb in the first place. Most importantly, what we
> > are missing is information about the object hash.
> > 
> > We don't do the same thing for the object database yet, but here we
> > essentially have the same problem. While the "files" database does not
> > need any information about the object format at creation time, alternate
> > backends are likely to require that information so that they can
> > properly set up their data structures.
> > 
> > Besides this forward-looking future proofing though, we also have a
> > second use case for deferring initialization of the object database,
> > namely alternates. When initializing the object database we do not yet
> > know whether we'll need alternates or not because this depends on the
> > repository we're about to clone from. If it is a local repository and
> > the user has passed "--refernce{,-if-able}", then we will end up writing
> > alternates into the object database.
> > 
> > The ugly part though is that we cannot determine where the repository is
> > getting cloned from before it has been initialized. While we of course
> > already have access to the user-provided URI, that URI can be very well
> > rewritten via "url.<base>.insteadOf". We can of course read the global-
> > and system-level configuration to resolve it. But we explicitly resolve
> > the URI a second time after we have initialized the repository because
> > it can happen that we copy a ".git/config" over from our templates, and
> > that file may cause us to rewrite the path.
> > 
> > In a subsequent commit though we'll start to write alternates as part of
> > the repository initialization, so we'll need to have the URI properly
> > resolved before we can initialize the object database. This is ugly, but
> > as mentioned above it makes sense for us to defer its initialization
> > anyway so that we also know about the object hash already.
> > 
> > Defer creation of the object database until after we have resolved the
> > URI.
> > 
> > Note that this also requires us to defer the call to `setup_reference()`
> > until after we have created the object database. While you might think
> > that this function has something to do with references ("refs/*"), it is
> > in fact responsible for setting up alternates. Consequently, we can only
> > call it after we have created the object database already.
> 
> Ah ok, so now that we are deferring ODB creation a bit further, we need
> to also move ref DB creation to afterwards too. Makes sense.

This is not about the reference database, `setup_reference()` sets up out alternates. But I think you just miswrote here, as you do mention alternates further down in your reply.

Patrick
Patrick SteinhardtSep 9, 2026, 05:48 UTC in reply to Patrick Steinhardt on lore

[PATCH v4 0/9] odb: write alternates at creation time

Hi,

writing alternates into the object database currently happens via `odb_source_write_alternate()`. But while that creates the ability to create alternates at arbitrary points of a source's lifetime, we don't use that functionality in the first place. Instead, we only ever write alternates when creating a new repository.

This design is suboptimal due to a couple of reasons:
  - It requires us to have a `write_alternates()` callback, which is
    overblown as we never even write alternates to an object database
    after it has been created.
  - We're about to make alternates an implementation detail of the
    object database's backend in a future patch series, so alternate
    implementations may not even support them.
  - The backend has more flexibility with how exactly alternates are
    configured when it itself is in full control over their setup at the
    time where it creates the object database itself.

This patch series thus refactors how we handle alternates so that we don't write them ad-hoc anymore. Instead, the series introduces a new option for `odb_source_create_on_disk()` that makes it handle those alternates at creation time.

This is part of the bigger goal of moving handling of alternates into the "files" backend.

This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24) with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop `alternates_db` field, 2026-08-17) merged into it.

Changes in v4:
  - Add documentation for the different functions that play a role in
    creating repositories.
  - Link to v3: https://patch.msgid.link/20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im
Changes in v3:
  - Refactor `init_db()` to not create the reference and object database
    at all anymore. Instead, it's now called `create_repository()` and
    it is responsible for creating the initial repository skeleton,
    only. This allows us to get rid of the flags and overall makes the
    logic more straight-forward by moving the command-specific logic
    into the respective commands.
  - A couple of typo fixes.
  - Link to v2: https://patch.msgid.link/20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im
Changes in v2:
  - Use a lockfile to write "info/alternates" during creation time.
  - Remove useless "strvec.h" include by reordering declarations a bit.
  - Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im
Thanks!
Patrick
---
Patrick Steinhardt (9):
      setup: split up concerns of `init_db()`
      builtin/clone: defer setup of the object database
      builtin/clone: move around `setup_reference()`
      builtin/clone: refactor handling of "--reference{,-if-able}"
      builtin/clone: move setup of alternates for shared local clones
      builtin/clone: move setup of alternates for non-shared local clones
      odb/source: support writing alternates when creating the database
      builtin/clone: write alternates via `odb_create_on_disk()`
      odb/source: remove the ability to write alternates
 builtin/clone.c            | 111 +++++++++++++++++++++++---------------
 builtin/init-db.c          |  32 ++++++++---
 odb.c                      |   9 ----
 odb.h                      |   7 ---
 odb/source-files.c         | 130 ++++++++++++++++++++++++++-------------------
 odb/source-inmemory.c      |   7 ---
 odb/source-loose.c         |   7 ---
 odb/source-packed.c        |   7 ---
 odb/source.h               |  43 +++++----------
 setup.c                    |  61 ++++++++-------------
 setup.h                    |  47 +++++++++++-----
 t/t5604-clone-reference.sh |  25 +++++++++
 12 files changed, 264 insertions(+), 222 deletions(-)
Range-diff versus v3:
 1:  b0e8d52235 !  1:  e6a17864eb setup: split up concerns of `init_db()`
    @@ setup.h: int apply_repository_format(struct repository *repo,
     -	    enum ref_storage_format ref_storage_format,
     -	    const char *initial_branch, int init_shared_repository,
     -	    unsigned int flags);
    ++/*
    ++ * Create the repository by creating the necessary directory structures,
    ++ * setting up the configuration and configuring the repository's format. If
    ++ * `template_dir` is set, copy over templates from that directory. Furthermore,
    ++ * if and only if `reinit_ok` is a non-NULL pointer, then the function may
    ++ * reinitialize a preexisting repository. In that case, the pointer will be set
    ++ * to `1` in case the repo was reinitialized and `0` if it didn't exist yet.
    ++ *
    ++ * Note that this function does not create the reference and object databases.
    ++ */
     +void create_repository(struct repository *repo,
     +		       const char *git_dir,
     +		       const char *real_git_dir,
    @@ setup.h: int apply_repository_format(struct repository *repo,
     +		       enum ref_storage_format ref_storage_format,
     +		       int init_shared_repository,
     +		       int *reinit_ok);
    ++
      void initialize_repository_version(struct repository *repo,
      				   int hash_algo,
      				   enum ref_storage_format ref_storage_format,
      				   int reinit);
    ++
    ++/*
    ++ * Create the reference database for the repository. The repository and its ref
    ++ * storage format must have already been configured properly before calling
    ++ * this function. When set, `initial_branch` overrides the default branch that
    ++ * HEAD will point to.
    ++ */
      void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
    -+void create_object_database(struct repository *repo);
      
    ++/*
    ++ * Create the object database for the repository. The repository must have
    ++ * already been configured properly before calling this function.
    ++ */
    ++void create_object_database(struct repository *repo);
    ++
      /*
       * NOTE NOTE NOTE!!
    +  *
 2:  4fc3a45d2d =  2:  45ae2231ad builtin/clone: defer setup of the object database
 3:  fd875b60b9 =  3:  92f00f1905 builtin/clone: move around `setup_reference()`
 4:  1ca710255b =  4:  b6860d41fb builtin/clone: refactor handling of "--reference{,-if-able}"
 5:  08a3a7a682 =  5:  070558b0b8 builtin/clone: move setup of alternates for shared local clones
 6:  afe84c063b =  6:  8e767a103a builtin/clone: move setup of alternates for non-shared local clones
 7:  8f0d3b13d5 =  7:  39ee1b828d odb/source: support writing alternates when creating the database
 8:  75d1e1e34c !  8:  531152ee93 builtin/clone: write alternates via `odb_create_on_disk()`
    @@ setup.c: static int create_default_files(struct repository *repo,
      	 * Create the "objects" directory in the common directory. This is done
     
      ## setup.h ##
    -@@ setup.h: void initialize_repository_version(struct repository *repo,
    - 				   enum ref_storage_format ref_storage_format,
    - 				   int reinit);
    - void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
    +@@ setup.h: void create_reference_database(struct repository *repo, const char *initial_bran
    + 
    + /*
    +  * Create the object database for the repository. The repository must have
    +- * already been configured properly before calling this function.
    ++ * already been configured properly before calling this function. When set,
    ++ * `alternates` is the list of alternates that should be written into the
    ++ * object database.
    +  */
     -void create_object_database(struct repository *repo);
     +void create_object_database(struct repository *repo, const struct strvec *alternates);
      
 9:  a674c3af3c =  9:  25bd93544e odb/source: remove the ability to write alternates

--- base-commit: afa255aeb620346d56a2c01fb5ae9163513c56d7 change-id: 20260813-pks-odb-write-alternates-at-creation-time-64010deb94a0

Patrick SteinhardtSep 9, 2026, 05:48 UTC in reply to Patrick Steinhardt on lore

[PATCH v4 1/9] setup: split up concerns of `init_db()`

The function `init_db()` is responsible for creating the on-disk directory structure required for a Git repository. It is used by both git-init(1) and git-clone(1), and because their expected behaviour is different we support a couple of flags:

  - The `QUIET` flag controls whether the command is quiet or not. For
    git-init(1) this is user-controllable, whereas for git-clone(1)
    we're always quiet.
  - The `EXIST_OK` flag controls whether a preexisting repository is
    okay or not. For git-init(1) it is, for git-clone(1) it's not.
  - The `SKIP_REFDB` flag controls whether the reference database should
    already be created or not. For git-init(1) we do, but for
    git-clone(1) we don't because it does not yet know about the default
    branch and about the remote object hash.

Furthermore, we're about to add another divergence in behaviour, where we have to also skip creation of the object database in git-clone(1). This is becoming quite cumbersome though.

Instead of introducing another flag, start to split up concerns of the function so that we never create the reference or object database. This becomes the responsibility of the caller, which is thus free to defer their creation to a later point in time. This lets us get rid of most of the divergent behaviour:

  - We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags
    anymore.
  - We don't need the `QUIET` flag anymore, as nothing prints output
    except for the final status message that tells the user that the
    repository has been (re)initialized. But as this message is specific
    to git-init(1), we can easily move it there.

The only piece of information we still have to convey is whether or not reinitialization of a preexisting repository is okay. This is handled by a new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay to reinitialize the repository. Furthermore, the pointer will be written to to indicate whether the repository was reinitialized or not, which we need in git-init(1) to print the correct initialization message.

With these refactorings, `init_db()` is named quite misleadingly though, as we don't create any of the reference or object databases anymore. Rename it to `create_repository()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c   |  9 +++++----
 builtin/init-db.c | 32 ++++++++++++++++++++++++--------
 setup.c           | 54 +++++++++++++++++-------------------------------------
 setup.h           | 45 +++++++++++++++++++++++++++++++++------------
 4 files changed, 79 insertions(+), 61 deletions(-)
Show changes to 4 files +79 −61

builtin/clone.c, builtin/init-db.c, setup.c, setup.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 5b25cca510..904d2d859f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1185,10 +1185,10 @@ int cmd_clone(int argc,
 	 * repository, and reference backends may persist that information into
 	 * their on-disk data structures.
 	 */
-	init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
-		GIT_HASH_UNKNOWN, ref_storage_format, NULL,
-		do_not_override_repo_unix_permissions,
-		INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
+	create_repository(the_repository, git_dir, real_git_dir, work_tree,
+			  option_template, GIT_HASH_UNKNOWN, ref_storage_format,
+			  do_not_override_repo_unix_permissions, NULL);
+	create_object_database(the_repository);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
@@ -1445,6 +1445,7 @@ int cmd_clone(int argc,
 	initialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);
 	repo_set_hash_algo(the_repository, hash_algo);
 	create_reference_database(the_repository, NULL, 1);
+	startup_info->have_repository = 1;
 
 	/*
 	 * Before fetching from the remote, download and install bundle
diff --git a/builtin/init-db.c b/builtin/init-db.c
index e96b1283b7..f2c7e3be6d 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -80,7 +80,7 @@ int cmd_init_db(int argc,
 	char *work_tree = NULL;
 	const char *template_dir = NULL;
 	char *template_dir_to_free = NULL;
-	unsigned int flags = 0;
+	int quiet = 0;
 	int bare = startup_info->force_bare_repository ? 1 : -1;
 	const char *object_format = NULL;
 	const char *ref_format = NULL;
@@ -102,7 +102,7 @@ int cmd_init_db(int argc,
 			.flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
 			.callback = shared_callback
 		},
-		OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
+		OPT_BOOL('q', "quiet", &quiet, N_("be quiet")),
 		OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
 			   N_("separate git dir from working tree")),
 		OPT_STRING('b', "initial-branch", &initial_branch, N_("name"),
@@ -113,7 +113,7 @@ int cmd_init_db(int argc,
 			   N_("specify the reference format to use")),
 		OPT_END()
 	};
-	int ret;
+	int reinit;
 
 	argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
 
@@ -247,14 +247,30 @@ int cmd_init_db(int argc,
 		die(_("--separate-git-dir incompatible with bare repository"));
 	}
 
-	flags |= INIT_DB_EXIST_OK;
-	ret = init_db(the_repository, git_dir, real_git_dir, work_tree,
-		      template_dir, hash_algo, ref_storage_format, initial_branch,
-		      init_shared_repository, flags);
+	create_repository(the_repository, git_dir, real_git_dir, work_tree,
+			  template_dir, hash_algo, ref_storage_format,
+			  init_shared_repository, &reinit);
+	create_reference_database(the_repository, initial_branch, quiet);
+	create_object_database(the_repository);
+
+	if (!quiet) {
+		int len = strlen(git_dir);
+
+		if (reinit)
+			printf(repo_settings_get_shared_repository(the_repository)
+			       ? _("Reinitialized existing shared Git repository in %s%s\n")
+			       : _("Reinitialized existing Git repository in %s%s\n"),
+			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+		else
+			printf(repo_settings_get_shared_repository(the_repository)
+			       ? _("Initialized empty shared Git repository in %s%s\n")
+			       : _("Initialized empty Git repository in %s%s\n"),
+			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+	}
 
 	free(template_dir_to_free);
 	free(real_git_dir_to_free);
 	free(work_tree);
 	free(git_dir);
-	return ret;
+	return 0;
 }
diff --git a/setup.c b/setup.c
index d90654f584..8c7b97f92e 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,
 	return reinit;
 }
 
-static void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo)
 {
 	/*
 	 * Create the "objects" directory in the common directory. This is done
@@ -2822,17 +2822,17 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 	}
 }
 
-int init_db(struct repository *repo,
-	    const char *git_dir,
-	    const char *real_git_dir,
-	    const char *worktree,
-	    const char *template_dir, int hash,
-	    enum ref_storage_format ref_storage_format,
-	    const char *initial_branch,
-	    int init_shared_repository, unsigned int flags)
+void create_repository(struct repository *repo,
+		       const char *git_dir,
+		       const char *real_git_dir,
+		       const char *worktree,
+		       const char *template_dir,
+		       int hash,
+		       enum ref_storage_format ref_storage_format,
+		       int init_shared_repository,
+		       int *reinit_ok)
 {
-	int reinit;
-	int exist_ok = flags & INIT_DB_EXIST_OK;
+	int reinit_ignored;
 	char *original_git_dir = real_pathdup(git_dir, 1);
 	struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
 	struct strbuf err = STRBUF_INIT;
@@ -2840,10 +2840,10 @@ int init_db(struct repository *repo,
 	if (real_git_dir) {
 		struct stat st;
 
-		if (!exist_ok && !stat(git_dir, &st))
+		if (!reinit_ok && !stat(git_dir, &st))
 			die(_("%s already exists"), git_dir);
 
-		if (!exist_ok && !stat(real_git_dir, &st))
+		if (!reinit_ok && !stat(real_git_dir, &st))
 			die(_("%s already exists"), real_git_dir);
 
 		apply_and_export_relative_gitdir(repo, real_git_dir, 1);
@@ -2877,8 +2877,10 @@ int init_db(struct repository *repo,
 
 	safe_create_dir(repo, git_dir, 0);
 
-	reinit = create_default_files(repo, template_dir, original_git_dir,
-				      &repo_fmt, init_shared_repository);
+	if (!reinit_ok)
+		reinit_ok = &reinit_ignored;
+	*reinit_ok = create_default_files(repo, template_dir, original_git_dir,
+					  &repo_fmt, init_shared_repository);
 
 	if (repo_settings_get_shared_repository(repo)) {
 		char buf[10];
@@ -2901,29 +2903,7 @@ int init_db(struct repository *repo,
 		repo_config_set(repo, "receive.denyNonFastforwards", "true");
 	}
 
-	if (!(flags & INIT_DB_SKIP_REFDB))
-		create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
-	create_object_database(repo);
-
-	startup_info->have_repository = 1;
-
-	if (!(flags & INIT_DB_QUIET)) {
-		int len = strlen(git_dir);
-
-		if (reinit)
-			printf(repo_settings_get_shared_repository(repo)
-			       ? _("Reinitialized existing shared Git repository in %s%s\n")
-			       : _("Reinitialized existing Git repository in %s%s\n"),
-			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
-		else
-			printf(repo_settings_get_shared_repository(repo)
-			       ? _("Initialized empty shared Git repository in %s%s\n")
-			       : _("Initialized empty Git repository in %s%s\n"),
-			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
-	}
-
 	clear_repository_format(&repo_fmt);
 	strbuf_release(&err);
 	free(original_git_dir);
-	return 0;
 }
diff --git a/setup.h b/setup.h
index 763fd384e8..f1c1ed65fb 100644
--- a/setup.h
+++ b/setup.h
@@ -256,24 +256,45 @@ int apply_repository_format(struct repository *repo,
 
 const char *get_template_dir(const char *option_template);
 
-#define INIT_DB_QUIET      (1 << 0)
-#define INIT_DB_EXIST_OK   (1 << 1)
-#define INIT_DB_SKIP_REFDB (1 << 2)
-
-int init_db(struct repository *repo,
-	    const char *git_dir,
-	    const char *real_git_dir,
-	    const char *worktree,
-	    const char *template_dir, int hash_algo,
-	    enum ref_storage_format ref_storage_format,
-	    const char *initial_branch, int init_shared_repository,
-	    unsigned int flags);
+/*
+ * Create the repository by creating the necessary directory structures,
+ * setting up the configuration and configuring the repository's format. If
+ * `template_dir` is set, copy over templates from that directory. Furthermore,
+ * if and only if `reinit_ok` is a non-NULL pointer, then the function may
+ * reinitialize a preexisting repository. In that case, the pointer will be set
+ * to `1` in case the repo was reinitialized and `0` if it didn't exist yet.
+ *
+ * Note that this function does not create the reference and object databases.
+ */
+void create_repository(struct repository *repo,
+		       const char *git_dir,
+		       const char *real_git_dir,
+		       const char *worktree,
+		       const char *template_dir,
+		       int hash_algo,
+		       enum ref_storage_format ref_storage_format,
+		       int init_shared_repository,
+		       int *reinit_ok);
+
 void initialize_repository_version(struct repository *repo,
 				   int hash_algo,
 				   enum ref_storage_format ref_storage_format,
 				   int reinit);
+
+/*
+ * Create the reference database for the repository. The repository and its ref
+ * storage format must have already been configured properly before calling
+ * this function. When set, `initial_branch` overrides the default branch that
+ * HEAD will point to.
+ */
 void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
 
+/*
+ * Create the object database for the repository. The repository must have
+ * already been configured properly before calling this function.
+ */
+void create_object_database(struct repository *repo);
+
 /*
  * NOTE NOTE NOTE!!
  *
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 9, 2026, 05:48 UTC in reply to Patrick Steinhardt on lore

[PATCH v4 2/9] builtin/clone: defer setup of the object database

When cloning a repository we defer initialization of the reference database. This is because we don't yet know all details required for us to initialize the refdb in the first place. Most importantly, what we are missing is information about the object hash.

We don't do the same thing for the object database yet, but here we essentially have the same problem. While the "files" database does not need any information about the object format at creation time, alternate backends are likely to require that information so that they can properly set up their data structures.

Besides this forward-looking future proofing though, we also have a second use case for deferring initialization of the object database, namely alternates. When initializing the object database we do not yet know whether we'll need alternates or not because this depends on the repository we're about to clone from. If it is a local repository and the user has passed "--refernce{,-if-able}", then we will end up writing alternates into the object database.

The ugly part though is that we cannot determine where the repository is getting cloned from before it has been initialized. While we of course already have access to the user-provided URI, that URI can be very well rewritten via "url.<base>.insteadOf". We can of course read the global- and system-level configuration to resolve it. But we explicitly resolve the URI a second time after we have initialized the repository because it can happen that we copy a ".git/config" over from our templates, and that file may cause us to rewrite the path.

In a subsequent commit though we'll start to write alternates as part of the repository initialization, so we'll need to have the URI properly resolved before we can initialize the object database. This is ugly, but as mentioned above it makes sense for us to defer its initialization anyway so that we also know about the object hash already.

Defer creation of the object database until after we have resolved the URI.

Note that this also requires us to defer the call to `setup_reference()` until after we have created the object database. While you might think that this function has something to do with references ("refs/*"), it is in fact responsible for setting up alternates. Consequently, we can only call it after we have created the object database already.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
Show changes to builtin/clone.c +4 −4
diff --git a/builtin/clone.c b/builtin/clone.c
index 904d2d859f..bdcbd7aa1b 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1188,7 +1188,6 @@ int cmd_clone(int argc,
 	create_repository(the_repository, git_dir, real_git_dir, work_tree,
 			  option_template, GIT_HASH_UNKNOWN, ref_storage_format,
 			  do_not_override_repo_unix_permissions, NULL);
-	create_object_database(the_repository);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
@@ -1311,9 +1310,6 @@ int cmd_clone(int argc,
 		strbuf_reset(&key);
 	}
 
-	if (option_required_reference.nr || option_optional_reference.nr)
-		setup_reference();
-
 	remote = remote_get_early(remote_name);
 
 	if (!option_rev)
@@ -1342,6 +1338,10 @@ int cmd_clone(int argc,
 	if (option_local > 0 && !is_local)
 		warning(_("--local is ignored"));
 
+	create_object_database(the_repository);
+	if (option_required_reference.nr || option_optional_reference.nr)
+		setup_reference();
+
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
 	transport->family = family;
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 9, 2026, 05:48 UTC in reply to Patrick Steinhardt on lore

[PATCH v4 3/9] builtin/clone: move around `setup_reference()`

In a subsequent commit, `setup_reference()` will start to call `copy_alternates()`. Prepare for this by moving the function further down so that we can avoid adding a declaration.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)
Show changes to builtin/clone.c +10 −10
diff --git a/builtin/clone.c b/builtin/clone.c
index bdcbd7aa1b..ac5843d7b9 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
 	return 0;
 }
 
-static void setup_reference(void)
-{
-	int required = 1;
-	for_each_string_list(&option_required_reference,
-			     add_one_reference, &required);
-	required = 0;
-	for_each_string_list(&option_optional_reference,
-			     add_one_reference, &required);
-}
-
 static void copy_alternates(struct strbuf *src, const char *src_repo)
 {
 	/*
@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
+static void setup_reference(void)
+{
+	int required = 1;
+	for_each_string_list(&option_required_reference,
+			     add_one_reference, &required);
+	required = 0;
+	for_each_string_list(&option_optional_reference,
+			     add_one_reference, &required);
+}
+
 static void mkdir_if_missing(const char *pathname, mode_t mode)
 {
 	struct stat st;
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 9, 2026, 05:48 UTC in reply to Patrick Steinhardt on lore

[PATCH v4 4/9] builtin/clone: refactor handling of "--reference{,-if-able}"

Users can pass "--reference{,-if-able}" to git-clone(1) to instruct it to set up alternates for the newly created repository. This allows it to reuse objects from the source repository so that in the best case we don't have to clone all objects over.

Those options are handled by the confusingly named `setup_reference()` function -- without the above context, one might rightfully believe that it was about refs, not about alternates. The function itself is rather simple: we loop through all provided alternate paths and then, if such an alternate is valid, we write it to the object database.

In subsequent commits we're about to consolidate the complete setup of alternates into this function, and furthermore we'll refactor the setup of the object database to handle doing this for us instead of writing the alternates into it one by one.

Prepare for this refactoring by collecting the alternates into a strvec. Rename the function to `collect_alternates()` to clarify its scope.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------
 1 file changed, 28 insertions(+), 16 deletions(-)
Show changes to builtin/clone.c +28 −16
diff --git a/builtin/clone.c b/builtin/clone.c
index ac5843d7b9..8786a49332 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
 	return canon;
 }
 
-static int add_one_reference(struct string_list_item *item, void *cb_data)
+struct add_one_alternate_data {
+	struct strvec *alternates;
+	int required;
+};
+
+static int add_one_alternate(struct string_list_item *item, void *cb_data)
 {
+	struct add_one_alternate_data *data = cb_data;
 	struct strbuf err = STRBUF_INIT;
-	int *required = cb_data;
 	char *ref_git = compute_alternate_path(item->string, &err);
 
 	if (!ref_git) {
-		if (*required)
+		if (data->required)
 			die("%s", err.buf);
 		else
 			fprintf(stderr,
 				_("info: Could not add alternate for '%s': %s\n"),
 				item->string, err.buf);
 	} else {
-		struct strbuf sb = STRBUF_INIT;
-		strbuf_addf(&sb, "%s/objects", ref_git);
-		odb_add_to_alternates_file(the_repository->objects, sb.buf);
-		strbuf_release(&sb);
+		strvec_pushf(data->alternates, "%s/objects", ref_git);
 	}
 
 	strbuf_release(&err);
@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
-static void setup_reference(void)
+static void collect_alternates(struct strvec *alternates)
 {
-	int required = 1;
-	for_each_string_list(&option_required_reference,
-			     add_one_reference, &required);
-	required = 0;
-	for_each_string_list(&option_optional_reference,
-			     add_one_reference, &required);
+	if (option_required_reference.nr || option_optional_reference.nr) {
+		struct add_one_alternate_data data = {
+			.alternates = alternates,
+			.required = 1,
+		};
+
+		for_each_string_list(&option_required_reference,
+				     add_one_alternate, &data);
+		data.required = 0;
+		for_each_string_list(&option_optional_reference,
+				     add_one_alternate, &data);
+	}
 }
 
 static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -999,6 +1007,7 @@ int cmd_clone(int argc,
 			   N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
 		OPT_END()
 	};
+	struct strvec alternates = STRVEC_INIT;
 
 	const char * const builtin_clone_usage[] = {
 		N_("git clone [<options>] [--] <repo> [<dir>]"),
@@ -1339,8 +1348,10 @@ int cmd_clone(int argc,
 		warning(_("--local is ignored"));
 
 	create_object_database(the_repository);
-	if (option_required_reference.nr || option_optional_reference.nr)
-		setup_reference();
+	collect_alternates(&alternates);
+
+	for (size_t i = 0; i < alternates.nr; i++)
+		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
 
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
@@ -1638,6 +1649,7 @@ int cmd_clone(int argc,
 	string_list_clear(&option_not, 0);
 	string_list_clear(&option_config, 0);
 	string_list_clear(&server_options, 0);
+	strvec_clear(&alternates);
 
 	free(remote_name);
 	strbuf_release(&reflog_msg);
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 9, 2026, 05:48 UTC in reply to Patrick Steinhardt on lore

[PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones

When cloning a local repository with "--shared" we add that repository to the new repository's alternates. This is done in `clone_local()`, which is responsible for performing local clones.

Move the logic into `collect_alternates()` to unify our setup of alternates. Furthermore, this will allow us to set up alternates right at creation time of the object database.

Note that the logic for cloning a local repository with "--no-shared" is not yet part of `collect_alternates()`. This will be handled in the next commit.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)
Show changes to builtin/clone.c +14 −9
diff --git a/builtin/clone.c b/builtin/clone.c
index 8786a49332..011fc867c8 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
-static void collect_alternates(struct strvec *alternates)
+static void collect_alternates(struct strvec *alternates,
+			       const char *src_repo, bool is_local)
 {
 	if (option_required_reference.nr || option_optional_reference.nr) {
 		struct add_one_alternate_data data = {
@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
 		for_each_string_list(&option_optional_reference,
 				     add_one_alternate, &data);
 	}
+
+	if (is_local) {
+		struct strbuf commondir = STRBUF_INIT;
+
+		get_common_dir(&commondir, src_repo);
+		if (option_shared)
+			strvec_pushf(alternates, "%s/objects", commondir.buf);
+
+		strbuf_release(&commondir);
+	}
 }
 
 static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 
 static void clone_local(const char *src_repo, const char *dest_repo)
 {
-	if (option_shared) {
-		struct strbuf alt = STRBUF_INIT;
-		get_common_dir(&alt, src_repo);
-		strbuf_addstr(&alt, "/objects");
-		odb_add_to_alternates_file(the_repository->objects, alt.buf);
-		strbuf_release(&alt);
-	} else {
+	if (!option_shared) {
 		struct strbuf src = STRBUF_INIT;
 		struct strbuf dest = STRBUF_INIT;
 		get_common_dir(&src, src_repo);
@@ -1348,7 +1353,7 @@ int cmd_clone(int argc,
 		warning(_("--local is ignored"));
 
 	create_object_database(the_repository);
-	collect_alternates(&alternates);
+	collect_alternates(&alternates, path, is_local);
 
 	for (size_t i = 0; i < alternates.nr; i++)
 		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 9, 2026, 05:48 UTC in reply to Patrick Steinhardt on lore

[PATCH v4 6/9] builtin/clone: move setup of alternates for non-shared local clones

Similar as in the preceding commit, move the setup of alternates for local clones with "--no-shared" into `collect_alternates()`. With this step, the complete setup of alternates is now handled by that function.

Note that besides moving stuff around, it also fixes a bug: previously, we did not know to resolve the referenced repository's common directory. Consequently, when referencing a worktree we failed to resolve alternates. But as `collect_alternates()` already knows to resolve the commondir for "--local" we can simply reuse this resolved path for our purpose.

Add two tests, the first one of which exercises this bug to avoid future regressions. The second test ensures that we properly handle relative alternates for a referenced worktree.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c            | 34 +++++++++++++++++++++++-----------
 t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++
 2 files changed, 48 insertions(+), 11 deletions(-)
Show changes to 2 files +48 −11

builtin/clone.c, t/t5604-clone-reference.sh

diff --git a/builtin/clone.c b/builtin/clone.c
index 011fc867c8..84c1317867 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)
 	return 0;
 }
 
-static void copy_alternates(struct strbuf *src, const char *src_repo)
+static void read_alternates(struct strvec *alternates, const char *src_repo)
 {
 	/*
 	 * Read from the source objects/info/alternates file
@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	 * to turn entries with paths relative to the original
 	 * absolute, so that they can be used in the new repository.
 	 */
-	FILE *in = xfopen(src->buf, "r");
+	FILE *in;
+	struct strbuf path = STRBUF_INIT;
 	struct strbuf line = STRBUF_INIT;
 
+	strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
+
+	in = fopen(path.buf, "r");
+	if (!in) {
+		if (errno == ENOENT)
+			goto out;
+		die_errno("could not read alternates file '%s'", path.buf);
+	}
+
 	while (strbuf_getline(&line, in) != EOF) {
 		char *abs_path;
 		if (!line.len || line.buf[0] == '#')
 			continue;
 		if (is_absolute_path(line.buf)) {
-			odb_add_to_alternates_file(the_repository->objects,
-						   line.buf);
+			strvec_push(alternates, line.buf);
 			continue;
 		}
 		abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
 		if (!normalize_path_copy(abs_path, abs_path))
-			odb_add_to_alternates_file(the_repository->objects,
-						   abs_path);
+			strvec_push(alternates, abs_path);
 		else
 			warning("skipping invalid relative alternate: %s/%s",
 				src_repo, line.buf);
 		free(abs_path);
 	}
+
+out:
+	strbuf_release(&path);
 	strbuf_release(&line);
-	fclose(in);
+	if (in)
+		fclose(in);
 }
 
 static void collect_alternates(struct strvec *alternates,
@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,
 		get_common_dir(&commondir, src_repo);
 		if (option_shared)
 			strvec_pushf(alternates, "%s/objects", commondir.buf);
+		else
+			read_alternates(alternates, commondir.buf);
 
 		strbuf_release(&commondir);
 	}
@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 			continue;
 		}
 
-		/* Files that cannot be copied bit-for-bit... */
-		if (!fspathcmp(iter->relative_path, "info/alternates")) {
-			copy_alternates(src, src_repo);
+		/* Alternates were already handled earlier. */
+		if (!fspathcmp(iter->relative_path, "info/alternates"))
 			continue;
-		}
 
 		if (unlink(dest->buf) && errno != ENOENT)
 			die_errno(_("failed to unlink '%s'"), dest->buf);
diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
index 39a0c318df..9e4b98fdb8 100755
--- a/t/t5604-clone-reference.sh
+++ b/t/t5604-clone-reference.sh
@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '
 	git clone --no-local --reference graph.git --dissociate orig clone
 '
 
+test_expect_success 'local clone from linked worktree carries over alternates' '
+	rm -fr base derived derived-wt dst expect &&
+	git init base &&
+	test_commit -C base one &&
+	git clone --shared base derived &&
+	git -C derived worktree add ../derived-wt &&
+	git clone derived-wt dst &&
+	echo "$(pwd)/base/.git/objects" >expect &&
+	test_cmp expect dst/.git/objects/info/alternates &&
+	git -C dst fsck
+'
+
+test_expect_success 'local clone from linked worktree resolves relative alternates' '
+	rm -fr base derived derived-wt dst expect &&
+	git init base &&
+	test_commit -C base one &&
+	git clone --shared base derived &&
+	echo "../../../base/.git/objects" >derived/.git/objects/info/alternates &&
+	git -C derived worktree add ../derived-wt &&
+	git clone derived-wt dst &&
+	echo "$(pwd)/base/.git/objects" >expect &&
+	test_cmp expect dst/.git/objects/info/alternates &&
+	git -C dst fsck
+'
+
 test_done
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 9, 2026, 05:48 UTC in reply to Patrick Steinhardt on lore

[PATCH v4 7/9] odb/source: support writing alternates when creating the database

Add the ability to write alternates when creating the object database. This change allows us to remove the `write_alternates()` callback in a subsequent patch.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 odb/source.h       | 17 +++++++++---
 setup.c            |  4 ++-
 3 files changed, 91 insertions(+), 6 deletions(-)
Show changes to 3 files +91 −6

odb/source-files.c, odb/source.h, setup.c

diff --git a/odb/source-files.c b/odb/source-files.c
index b7b3a297bb..8fe65d91f8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -18,6 +18,7 @@
 #include "run-command.h"
 #include "strbuf.h"
 #include "string-list.h"
+#include "strmap.h"
 #include "strvec.h"
 #include "tree.h"
 #include "write-or-die.h"
@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)
 	odb_source_close(&files->packed->base);
 }
 
-static int odb_source_files_create_on_disk(struct odb_source *source)
+static int odb_source_files_create_on_disk(struct odb_source *source,
+					   const struct odb_create_on_disk_options *opts)
 {
+	struct lock_file alternates_lock = LOCK_INIT;
 	struct strbuf path = STRBUF_INIT;
+	struct strset seen = STRSET_INIT;
+	struct strbuf line = STRBUF_INIT;
+	int ret;
 
 	safe_create_dir(source->odb->repo, source->path, 1);
 
@@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
 	strbuf_addf(&path, "%s/info", source->path);
 	safe_create_dir(source->odb->repo, path.buf, 1);
 
+	if (opts->alternates && opts->alternates->nr) {
+		FILE *alternates, *orig;
+
+		strbuf_reset(&path);
+		strbuf_addf(&path, "%s/info/alternates", source->path);
+
+		repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,
+					       path.buf, LOCK_DIE_ON_ERROR);
+
+		alternates = fdopen_lock_file(&alternates_lock, "w");
+		if (!alternates) {
+			ret = error_errno(_("unable to fdopen alternates lockfile"));
+			goto out;
+		}
+
+		/*
+		 * The alternates file may already exist, e.g. when it has been
+		 * seeded from a template directory. Read any preexisting
+		 * entries so that we don't end up writing duplicates.
+		 */
+		orig = fopen(path.buf, "r");
+		if (orig) {
+			while (strbuf_getline(&line, orig) != EOF) {
+				strset_add(&seen, line.buf);
+				fprintf(alternates, "%s\n", line.buf);
+			}
+
+			if (ferror(orig)) {
+				ret = error_errno(_("unable to read alternates file"));
+				fclose(orig);
+				goto out;
+			}
+
+			fclose(orig);
+		} else if (errno != ENOENT) {
+			ret = error_errno(_("unable to read alternates file"));
+			goto out;
+		}
+
+		for (size_t i = 0; i < opts->alternates->nr; i++) {
+			const char *alternate = opts->alternates->v[i];
+			if (!strset_add(&seen, alternate))
+				continue;
+			fprintf(alternates, "%s\n", alternate);
+		}
+
+		if (ferror(alternates)) {
+			ret = error_errno(_("unable to write alternates file"));
+			goto out;
+		}
+
+		if (commit_lock_file(&alternates_lock)) {
+			ret = error_errno(_("unable to commit alternates file"));
+			goto out;
+		}
+	}
+
+	/* Reprepare the object database to activate alternates. */
+	odb_reprepare(source->odb);
+
+	ret = 0;
+
+out:
+	rollback_lock_file(&alternates_lock);
+	strbuf_release(&line);
 	strbuf_release(&path);
-	return 0;
+	strset_clear(&seen);
+	return ret;
 }
 
 static void odb_source_files_prepare(struct odb_source *source,
diff --git a/odb/source.h b/odb/source.h
index ea8675247e..63f1c0c531 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -36,6 +36,15 @@ struct object_id;
 struct odb_stream;
 struct strvec;
 
+struct odb_create_on_disk_options {
+	/*
+	 * Alternates that shall be written into the newly created object
+	 * database. Whether or not this option can be handled is specific to
+	 * the backend.
+	 */
+	const struct strvec *alternates;
+};
+
 /*
  * The source is the part of the object database that stores the actual
  * objects. It thus encapsulates the logic to read and write the specific
@@ -106,7 +115,8 @@ struct odb_source {
 	 * This callback may be NULL in case the source does not need any
 	 * on-disk setup.
 	 */
-	int (*create_on_disk)(struct odb_source *source);
+	int (*create_on_disk)(struct odb_source *source,
+			      const struct odb_create_on_disk_options *opts);
 
 	/*
 	 * This callback is expected to prepare the source so that it becomes
@@ -356,11 +366,12 @@ static inline void odb_source_close(struct odb_source *source)
  * Create on-disk data structures that are required for this source to operate
  * correctly. Returns 0 on success, a negative error code otherwise.
  */
-static inline int odb_source_create_on_disk(struct odb_source *source)
+static inline int odb_source_create_on_disk(struct odb_source *source,
+					    const struct odb_create_on_disk_options *opts)
 {
 	if (!source->create_on_disk)
 		return 0;
-	return source->create_on_disk(source);
+	return source->create_on_disk(source, opts);
 }
 
 /*
diff --git a/setup.c b/setup.c
index 8c7b97f92e..37a8e6f124 100644
--- a/setup.c
+++ b/setup.c
@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,
 
 void create_object_database(struct repository *repo)
 {
+	struct odb_create_on_disk_options opts = { 0 };
+
 	/*
 	 * Create the "objects" directory in the common directory. This is done
 	 * so that the repository can be discovered regardless of the backend
@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)
 
 	repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);
 
-	if (odb_source_create_on_disk(repo->objects->sources) < 0)
+	if (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)
 		die(_("failed creating object database"));
 }
 
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 9, 2026, 05:48 UTC in reply to Patrick Steinhardt on lore

[PATCH v4 8/9] builtin/clone: write alternates via `odb_create_on_disk()`

When creating a repository with alternates we first initialize the object database and then write alternates to it in a separate step. This is unfortunate due to a couple of reasons:

  - It requires us to have a `write_alternates()` callback, which is
    unfortunate as we never even write alternates to an object database
    after it has been created.
  - We're about to make alternates an implementation detail of the
    object database's backend in a future patch series, so having this
    callback is suboptimal there.
  - The backend has more flexibility with how exactly alternates are
    configured when it itself is in full control over their setup at the
    time where it creates the object database itself.

We have thus introduced the ability to write alternates right at creation time in the preceding commits, and we have unified setup of alternates into a single location. All that's left to do for us now is to wire up alternates as an option for the database creation.

Do so.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c   | 5 +----
 builtin/init-db.c | 2 +-
 setup.c           | 7 +++++--
 setup.h           | 6 ++++--
 4 files changed, 11 insertions(+), 9 deletions(-)
Show changes to 4 files +11 −9

builtin/clone.c, builtin/init-db.c, setup.c, setup.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 84c1317867..9e84646845 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1364,11 +1364,8 @@ int cmd_clone(int argc,
 	if (option_local > 0 && !is_local)
 		warning(_("--local is ignored"));
 
-	create_object_database(the_repository);
 	collect_alternates(&alternates, path, is_local);
-
-	for (size_t i = 0; i < alternates.nr; i++)
-		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
+	create_object_database(the_repository, &alternates);
 
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index f2c7e3be6d..5c22eae2f3 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -251,7 +251,7 @@ int cmd_init_db(int argc,
 			  template_dir, hash_algo, ref_storage_format,
 			  init_shared_repository, &reinit);
 	create_reference_database(the_repository, initial_branch, quiet);
-	create_object_database(the_repository);
+	create_object_database(the_repository, NULL);
 
 	if (!quiet) {
 		int len = strlen(git_dir);
diff --git a/setup.c b/setup.c
index 37a8e6f124..17d0d25973 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,
 	return reinit;
 }
 
-void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo,
+			    const struct strvec *alternates)
 {
-	struct odb_create_on_disk_options opts = { 0 };
+	struct odb_create_on_disk_options opts = {
+		.alternates = alternates,
+	};
 
 	/*
 	 * Create the "objects" directory in the common directory. This is done
diff --git a/setup.h b/setup.h
index f1c1ed65fb..27b2492373 100644
--- a/setup.h
+++ b/setup.h
@@ -291,9 +291,11 @@ void create_reference_database(struct repository *repo, const char *initial_bran
 
 /*
  * Create the object database for the repository. The repository must have
- * already been configured properly before calling this function.
+ * already been configured properly before calling this function. When set,
+ * `alternates` is the list of alternates that should be written into the
+ * object database.
  */
-void create_object_database(struct repository *repo);
+void create_object_database(struct repository *repo, const struct strvec *alternates);
 
 /*
  * NOTE NOTE NOTE!!
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 9, 2026, 05:48 UTC in reply to Patrick Steinhardt on lore

[PATCH v4 9/9] odb/source: remove the ability to write alternates

There are no users of `odb_source_write_alternates()` in our tree anymore. Remove that function and its supporting infrastructure.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb.c                 |  9 ---------
 odb.h                 |  7 -------
 odb/source-files.c    | 54 ---------------------------------------------------
 odb/source-inmemory.c |  7 -------
 odb/source-loose.c    |  7 -------
 odb/source-packed.c   |  7 -------
 odb/source.h          | 26 -------------------------
 7 files changed, 117 deletions(-)
Show changes to 7 files +0 −117

odb.c, odb.h, odb/source-files.c, odb/source-inmemory.c, odb/source-loose.c, odb/source-packed.c, odb/source.h

diff --git a/odb.c b/odb.c
index 67d98d64fc..b531cf8fb3 100644
--- a/odb.c
+++ b/odb.c
@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
 	return alternate;
 }
 
-void odb_add_to_alternates_file(struct object_database *odb,
-				const char *dir)
-{
-	int ret = odb_source_write_alternate(odb->sources, dir);
-	if (ret < 0)
-		die(NULL);
-	odb_add_alternate_recursively(odb, dir, 0);
-}
-
 struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
 						const char *dir)
 {
diff --git a/odb.h b/odb.h
index b9e0db56ec..2d002461f8 100644
--- a/odb.h
+++ b/odb.h
@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,
  */
 int odb_has_alternates(struct object_database *odb);
 
-/*
- * Add the directory to the on-disk alternates file; the new entry will also
- * take effect in the current process.
- */
-void odb_add_to_alternates_file(struct object_database *odb,
-				const char *dir);
-
 /*
  * Add the directory to the in-memory list of alternate sources (along with any
  * recursive alternates it points to), but do not modify the on-disk alternates
diff --git a/odb/source-files.c b/odb/source-files.c
index 8fe65d91f8..b3f340dff8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -306,59 +306,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,
 	return 0;
 }
 
-static int odb_source_files_write_alternate(struct odb_source *source,
-					    const char *alternate)
-{
-	struct lock_file lock = LOCK_INIT;
-	char *path = xstrfmt("%s/%s", source->path, "info/alternates");
-	FILE *in, *out;
-	int found = 0;
-	int ret;
-
-	repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
-				       LOCK_DIE_ON_ERROR);
-	out = fdopen_lock_file(&lock, "w");
-	if (!out) {
-		ret = error_errno(_("unable to fdopen alternates lockfile"));
-		goto out;
-	}
-
-	in = fopen(path, "r");
-	if (in) {
-		struct strbuf line = STRBUF_INIT;
-
-		while (strbuf_getline(&line, in) != EOF) {
-			if (!strcmp(alternate, line.buf)) {
-				found = 1;
-				break;
-			}
-			fprintf_or_die(out, "%s\n", line.buf);
-		}
-
-		strbuf_release(&line);
-		fclose(in);
-	} else if (errno != ENOENT) {
-		ret = error_errno(_("unable to read alternates file"));
-		goto out;
-	}
-
-	if (found) {
-		rollback_lock_file(&lock);
-	} else {
-		fprintf_or_die(out, "%s\n", alternate);
-		if (commit_lock_file(&lock)) {
-			ret = error_errno(_("unable to move new alternates file into place"));
-			goto out;
-		}
-	}
-
-	ret = 0;
-
-out:
-	free(path);
-	return ret;
-}
-
 static int too_many_loose_objects(struct odb_source_files *files, int limit)
 {
 	unsigned long loose_count;
@@ -842,7 +789,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
 	files->base.write_object_stream = odb_source_files_write_object_stream;
 	files->base.begin_transaction = odb_source_files_begin_transaction;
 	files->base.read_alternates = odb_source_files_read_alternates;
-	files->base.write_alternate = odb_source_files_write_alternate;
 	files->base.optimize = odb_source_files_optimize;
 	files->base.optimize_required = odb_source_files_optimize_required;
 
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
index 795672adf2..b00248dfb2 100644
--- a/odb/source-inmemory.c
+++ b/odb/source-inmemory.c
@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,
-					       const char *alternate UNUSED)
-{
-	return error("in-memory source does not support alternates");
-}
-
 static void odb_source_inmemory_close(struct odb_source *source UNUSED)
 {
 }
@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
 	source->base.freshen_object = odb_source_inmemory_freshen_object;
 	source->base.begin_transaction = odb_source_inmemory_begin_transaction;
 	source->base.read_alternates = odb_source_inmemory_read_alternates;
-	source->base.write_alternate = odb_source_inmemory_write_alternate;
 
 	return source;
 }
diff --git a/odb/source-loose.c b/odb/source-loose.c
index bb3455dfbd..0f9b30bac1 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,
-					    const char *alternate UNUSED)
-{
-	return error("loose source does not support alternates");
-}
-
 static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
 {
 	oidtree_clear(loose->cache);
@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
 	loose->base.write_object_stream = odb_source_loose_write_object_stream;
 	loose->base.begin_transaction = odb_source_loose_begin_transaction;
 	loose->base.read_alternates = odb_source_loose_read_alternates;
-	loose->base.write_alternate = odb_source_loose_write_alternate;
 
 	if (!is_absolute_path(loose->base.path))
 		chdir_notify_register(NULL, odb_source_loose_reparent, loose);
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 630d955585..c2d253759c 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,
-					     const char *alternate UNUSED)
-{
-	return error("packed backend cannot write alternates");
-}
-
 void (*report_garbage)(unsigned seen_bits, const char *path);
 
 static void report_helper(const struct string_list *list,
@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
 	packed->base.write_object_stream = odb_source_packed_write_object_stream;
 	packed->base.begin_transaction = odb_source_packed_begin_transaction;
 	packed->base.read_alternates = odb_source_packed_read_alternates;
-	packed->base.write_alternate = odb_source_packed_write_alternate;
 
 	if (!is_absolute_path(path))
 		chdir_notify_register(NULL, odb_source_packed_reparent, packed);
diff --git a/odb/source.h b/odb/source.h
index 63f1c0c531..693a9fc604 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -286,19 +286,6 @@ struct odb_source {
 	int (*read_alternates)(struct odb_source *source,
 			       struct strvec *out);
 
-	/*
-	 * This callback is expected to persist the singular alternate passed
-	 * to it into its list of alternates. Any pre-existing alternates are
-	 * expected to remain active. Subsequent calls to `read_alternates` are
-	 * thus expected to yield the pre-existing list of alternates plus the
-	 * newly added alternate appended to its end.
-	 *
-	 * The callback is expected to return 0 on success, a negative error
-	 * code otherwise.
-	 */
-	int (*write_alternate)(struct odb_source *source,
-			       const char *alternate);
-
 	/*
 	 * This callback is expected to optimize the object database source.
 	 * Returns 0 on success, a negative error code otherwise.
@@ -518,19 +505,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,
 	return source->read_alternates(source, out);
 }
 
-/*
- * Write and persist a new alternate object database source for the given
- * source. Any preexisting alternates are expected to stay valid, and the new
- * alternate shall be appended to the end of the list.
- *
- * Returns 0 on success, a negative error code otherwise.
- */
-static inline int odb_source_write_alternate(struct odb_source *source,
-					      const char *alternate)
-{
-	return source->write_alternate(source, alternate);
-}
-
 /*
  * Create a new transaction that can be used to write objects into a temporary
  * staging area. The objects will only be persisted when the transaction is
-- 
2.55.0.1074.ge7621b4bad.dirty
Justin ToblerSep 9, 2026, 18:30 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v4 0/9] odb: write alternates at creation time

On 26/09/09 07:48AM, Patrick Steinhardt wrote:
> Changes in v4:
>   - Add documentation for the different functions that play a role in
>     creating repositories.
Thanks. This version looks good to me.
-Justin
Karthik NayakSep 10, 2026, 09:24 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v4 1/9] setup: split up concerns of `init_db()`

Patrick Steinhardt <ps@pks.im> writes:
Show 54 quoted lines
> The function `init_db()` is responsible for creating the on-disk
> directory structure required for a Git repository. It is used by both
> git-init(1) and git-clone(1), and because their expected behaviour is
> different we support a couple of flags:
>
>   - The `QUIET` flag controls whether the command is quiet or not. For
>     git-init(1) this is user-controllable, whereas for git-clone(1)
>     we're always quiet.
>
>   - The `EXIST_OK` flag controls whether a preexisting repository is
>     okay or not. For git-init(1) it is, for git-clone(1) it's not.
>
>   - The `SKIP_REFDB` flag controls whether the reference database should
>     already be created or not. For git-init(1) we do, but for
>     git-clone(1) we don't because it does not yet know about the default
>     branch and about the remote object hash.
>
> Furthermore, we're about to add another divergence in behaviour, where
> we have to also skip creation of the object database in git-clone(1).
> This is becoming quite cumbersome though.
>
> Instead of introducing another flag, start to split up concerns of the
> function so that we never create the reference or object database. This
> becomes the responsibility of the caller, which is thus free to defer
> their creation to a later point in time. This lets us get rid of most of
> the divergent behaviour:
>
>   - We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags
>     anymore.
>
>   - We don't need the `QUIET` flag anymore, as nothing prints output
>     except for the final status message that tells the user that the
>     repository has been (re)initialized. But as this message is specific
>     to git-init(1), we can easily move it there.
>
> The only piece of information we still have to convey is whether or not
> reinitialization of a preexisting repository is okay. This is handled by
> a new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay
> to reinitialize the repository. Furthermore, the pointer will be written
> to to indicate whether the repository was reinitialized or not, which we
> need in git-init(1) to print the correct initialization message.
>
> With these refactorings, `init_db()` is named quite misleadingly though,
> as we don't create any of the reference or object databases anymore.
> Rename it to `create_repository()`.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/clone.c   |  9 +++++----
>  builtin/init-db.c | 32 ++++++++++++++++++++++++--------
>  setup.c           | 54 +++++++++++++++++-------------------------------------
>  setup.h           | 45 +++++++++++++++++++++++++++++++++------------
>  4 files changed, 79 insertions(+), 61 deletions(-)
>
[snip]
Show 38 quoted lines
> @@ -2877,8 +2877,10 @@ int init_db(struct repository *repo,
>
>  	safe_create_dir(repo, git_dir, 0);
>
> -	reinit = create_default_files(repo, template_dir, original_git_dir,
> -				      &repo_fmt, init_shared_repository);
> +	if (!reinit_ok)
> +		reinit_ok = &reinit_ignored;
> +	*reinit_ok = create_default_files(repo, template_dir, original_git_dir,
> +					  &repo_fmt, init_shared_repository);
>
>  	if (repo_settings_get_shared_repository(repo)) {
>  		char buf[10];
> @@ -2901,29 +2903,7 @@ int init_db(struct repository *repo,
>  		repo_config_set(repo, "receive.denyNonFastforwards", "true");
>  	}
>
> -	if (!(flags & INIT_DB_SKIP_REFDB))
> -		create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
> -	create_object_database(repo);
> -
> -	startup_info->have_repository = 1;
> -
> -	if (!(flags & INIT_DB_QUIET)) {
> -		int len = strlen(git_dir);
> -
> -		if (reinit)
> -			printf(repo_settings_get_shared_repository(repo)
> -			       ? _("Reinitialized existing shared Git repository in %s%s\n")
> -			       : _("Reinitialized existing Git repository in %s%s\n"),
> -			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
> -		else
> -			printf(repo_settings_get_shared_repository(repo)
> -			       ? _("Initialized empty shared Git repository in %s%s\n")
> -			       : _("Initialized empty Git repository in %s%s\n"),
> -			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
> -	}
> -

I was wondering if the order of initialization changes with the move, but it stays the same. So all the changes look good.

[snip]
Karthik NayakSep 10, 2026, 09:28 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v4 2/9] builtin/clone: defer setup of the object database

Patrick Steinhardt <ps@pks.im> writes:
Show 19 quoted lines
> When cloning a repository we defer initialization of the reference
> database. This is because we don't yet know all details required for us
> to initialize the refdb in the first place. Most importantly, what we
> are missing is information about the object hash.
>
> We don't do the same thing for the object database yet, but here we
> essentially have the same problem. While the "files" database does not
> need any information about the object format at creation time, alternate
> backends are likely to require that information so that they can
> properly set up their data structures.
>
> Besides this forward-looking future proofing though, we also have a
> second use case for deferring initialization of the object database,
> namely alternates. When initializing the object database we do not yet
> know whether we'll need alternates or not because this depends on the
> repository we're about to clone from. If it is a local repository and
> the user has passed "--refernce{,-if-able}", then we will end up writing
> alternates into the object database.
>
Nit: s/refernce/reference
Show 24 quoted lines
> The ugly part though is that we cannot determine where the repository is
> getting cloned from before it has been initialized. While we of course
> already have access to the user-provided URI, that URI can be very well
> rewritten via "url.<base>.insteadOf". We can of course read the global-
> and system-level configuration to resolve it. But we explicitly resolve
> the URI a second time after we have initialized the repository because
> it can happen that we copy a ".git/config" over from our templates, and
> that file may cause us to rewrite the path.
>
> In a subsequent commit though we'll start to write alternates as part of
> the repository initialization, so we'll need to have the URI properly
> resolved before we can initialize the object database. This is ugly, but
> as mentioned above it makes sense for us to defer its initialization
> anyway so that we also know about the object hash already.
>
> Defer creation of the object database until after we have resolved the
> URI.
>
> Note that this also requires us to defer the call to `setup_reference()`
> until after we have created the object database. While you might think
> that this function has something to do with references ("refs/*"), it is
> in fact responsible for setting up alternates. Consequently, we can only
> call it after we have created the object database already.
>

Haha. I like this last para, you kinda explained the thought I was getting as I was getting it :)

Show 24 quoted lines
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/clone.c | 8 ++++----
>  1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 904d2d859f..bdcbd7aa1b 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -1188,7 +1188,6 @@ int cmd_clone(int argc,
>  	create_repository(the_repository, git_dir, real_git_dir, work_tree,
>  			  option_template, GIT_HASH_UNKNOWN, ref_storage_format,
>  			  do_not_override_repo_unix_permissions, NULL);
> -	create_object_database(the_repository);
>
>  	if (real_git_dir) {
>  		free((char *)git_dir);
> @@ -1311,9 +1310,6 @@ int cmd_clone(int argc,
>  		strbuf_reset(&key);
>  	}
>
> -	if (option_required_reference.nr || option_optional_reference.nr)
> -		setup_reference();
> -

So this is the alternate setup, which we move down to after the `create_object_database()`. Looks good.

Show 17 quoted lines
>  	remote = remote_get_early(remote_name);
>
>  	if (!option_rev)
> @@ -1342,6 +1338,10 @@ int cmd_clone(int argc,
>  	if (option_local > 0 && !is_local)
>  		warning(_("--local is ignored"));
>
> +	create_object_database(the_repository);
> +	if (option_required_reference.nr || option_optional_reference.nr)
> +		setup_reference();
> +
>  	transport = transport_get(remote, path ? path : remote->url.v[0]);
>  	transport_set_verbosity(transport, option_verbosity, option_progress);
>  	transport->family = family;
>
> --
> 2.55.0.1074.ge7621b4bad.dirty
Karthik NayakSep 10, 2026, 09:29 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v4 3/9] builtin/clone: move around `setup_reference()`

Patrick Steinhardt <ps@pks.im> writes:
> In a subsequent commit, `setup_reference()` will start to call
> `copy_alternates()`. Prepare for this by moving the function further
> down so that we can avoid adding a declaration.
>

Will we rename setup_reference() now that we agreed it is a bit ambiguous, let's see..

[snip]
Karthik NayakSep 10, 2026, 09:37 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v4 4/9] builtin/clone: refactor handling of "--reference{,-if-able}"

Patrick Steinhardt <ps@pks.im> writes:
Show 19 quoted lines
> Users can pass "--reference{,-if-able}" to git-clone(1) to instruct it
> to set up alternates for the newly created repository. This allows it to
> reuse objects from the source repository so that in the best case we
> don't have to clone all objects over.
>
> Those options are handled by the confusingly named `setup_reference()`
> function -- without the above context, one might rightfully believe that
> it was about refs, not about alternates. The function itself is rather
> simple: we loop through all provided alternate paths and then, if such
> an alternate is valid, we write it to the object database.
>
> In subsequent commits we're about to consolidate the complete setup of
> alternates into this function, and furthermore we'll refactor the setup
> of the object database to handle doing this for us instead of writing
> the alternates into it one by one.
>
> Prepare for this refactoring by collecting the alternates into a strvec.
> Rename the function to `collect_alternates()` to clarify its scope.
>
Yay!
Show 15 quoted lines
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------
>  1 file changed, 28 insertions(+), 16 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index ac5843d7b9..8786a49332 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
>  	return canon;
>  }
>
> -static int add_one_reference(struct string_list_item *item, void *cb_data)
> +struct add_one_alternate_data {

So this is `add_one_alternate()`'s data, was a bit confusing cause I first read that this was information regarding a single alternate, but then it also has a field called `alternates`.

> +	struct strvec *alternates;
> +	int required;
Question: here and other places, I see some of the boolean-ish fields
being declared as `int`. It doesn't matter, but is there a reason?
Show 51 quoted lines
> +};
> +
> +static int add_one_alternate(struct string_list_item *item, void *cb_data)
>  {
> +	struct add_one_alternate_data *data = cb_data;
>  	struct strbuf err = STRBUF_INIT;
> -	int *required = cb_data;
>  	char *ref_git = compute_alternate_path(item->string, &err);
>
>  	if (!ref_git) {
> -		if (*required)
> +		if (data->required)
>  			die("%s", err.buf);
>  		else
>  			fprintf(stderr,
>  				_("info: Could not add alternate for '%s': %s\n"),
>  				item->string, err.buf);
>  	} else {
> -		struct strbuf sb = STRBUF_INIT;
> -		strbuf_addf(&sb, "%s/objects", ref_git);
> -		odb_add_to_alternates_file(the_repository->objects, sb.buf);
> -		strbuf_release(&sb);
> +		strvec_pushf(data->alternates, "%s/objects", ref_git);
>  	}
>
>  	strbuf_release(&err);
> @@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
>  	fclose(in);
>  }
>
> -static void setup_reference(void)
> +static void collect_alternates(struct strvec *alternates)
>  {
> -	int required = 1;
> -	for_each_string_list(&option_required_reference,
> -			     add_one_reference, &required);
> -	required = 0;
> -	for_each_string_list(&option_optional_reference,
> -			     add_one_reference, &required);
> +	if (option_required_reference.nr || option_optional_reference.nr) {
> +		struct add_one_alternate_data data = {
> +			.alternates = alternates,
> +			.required = 1,
> +		};
> +
> +		for_each_string_list(&option_required_reference,
> +				     add_one_alternate, &data);
> +		data.required = 0;
> +		for_each_string_list(&option_optional_reference,
> +				     add_one_alternate, &data);
> +	}
Nit: might be nicer to read
     if (!option_required_reference.nr && !option_optional_reference.nr)
        return;
The rest of it doesn't have to be in the `if` block.
Show 22 quoted lines
>  }
>
>  static void mkdir_if_missing(const char *pathname, mode_t mode)
> @@ -999,6 +1007,7 @@ int cmd_clone(int argc,
>  			   N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
>  		OPT_END()
>  	};
> +	struct strvec alternates = STRVEC_INIT;
>
>  	const char * const builtin_clone_usage[] = {
>  		N_("git clone [<options>] [--] <repo> [<dir>]"),
> @@ -1339,8 +1348,10 @@ int cmd_clone(int argc,
>  		warning(_("--local is ignored"));
>
>  	create_object_database(the_repository);
> -	if (option_required_reference.nr || option_optional_reference.nr)
> -		setup_reference();
> +	collect_alternates(&alternates);
> +
> +	for (size_t i = 0; i < alternates.nr; i++)
> +		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
>
So now adding the alternates is moved out of `collect_alternates()`.
Nit: might be nice to mention this in the commit message.
Show 13 quoted lines
>  	transport = transport_get(remote, path ? path : remote->url.v[0]);
>  	transport_set_verbosity(transport, option_verbosity, option_progress);
> @@ -1638,6 +1649,7 @@ int cmd_clone(int argc,
>  	string_list_clear(&option_not, 0);
>  	string_list_clear(&option_config, 0);
>  	string_list_clear(&server_options, 0);
> +	strvec_clear(&alternates);
>
>  	free(remote_name);
>  	strbuf_release(&reflog_msg);
>
> --
> 2.55.0.1074.ge7621b4bad.dirty
Karthik NayakSep 10, 2026, 10:52 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones

Patrick Steinhardt <ps@pks.im> writes:
Show 38 quoted lines
> When cloning a local repository with "--shared" we add that repository
> to the new repository's alternates. This is done in `clone_local()`,
> which is responsible for performing local clones.
>
> Move the logic into `collect_alternates()` to unify our setup of
> alternates. Furthermore, this will allow us to set up alternates right
> at creation time of the object database.
>
> Note that the logic for cloning a local repository with "--no-shared" is
> not yet part of `collect_alternates()`. This will be handled in the next
> commit.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/clone.c | 23 ++++++++++++++---------
>  1 file changed, 14 insertions(+), 9 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 8786a49332..011fc867c8 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
>  	fclose(in);
>  }
>
> -static void collect_alternates(struct strvec *alternates)
> +static void collect_alternates(struct strvec *alternates,
> +			       const char *src_repo, bool is_local)
>  {
>  	if (option_required_reference.nr || option_optional_reference.nr) {
>  		struct add_one_alternate_data data = {
> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
>  		for_each_string_list(&option_optional_reference,
>  				     add_one_alternate, &data);
>  	}
> +
> +	if (is_local) {
>
Shouldn't we also check for `option_shared` here?
Show 38 quoted lines
> +		struct strbuf commondir = STRBUF_INIT;
> +
> +		get_common_dir(&commondir, src_repo);
> +		if (option_shared)
> +			strvec_pushf(alternates, "%s/objects", commondir.buf);
> +
> +		strbuf_release(&commondir);
> +	}
>  }
>
>  static void mkdir_if_missing(const char *pathname, mode_t mode)
> @@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
>
>  static void clone_local(const char *src_repo, const char *dest_repo)
>  {
> -	if (option_shared) {
> -		struct strbuf alt = STRBUF_INIT;
> -		get_common_dir(&alt, src_repo);
> -		strbuf_addstr(&alt, "/objects");
> -		odb_add_to_alternates_file(the_repository->objects, alt.buf);
> -		strbuf_release(&alt);
> -	} else {
> +	if (!option_shared) {
>  		struct strbuf src = STRBUF_INIT;
>  		struct strbuf dest = STRBUF_INIT;
>  		get_common_dir(&src, src_repo);
> @@ -1348,7 +1353,7 @@ int cmd_clone(int argc,
>  		warning(_("--local is ignored"));
>
>  	create_object_database(the_repository);
> -	collect_alternates(&alternates);
> +	collect_alternates(&alternates, path, is_local);
>
>  	for (size_t i = 0; i < alternates.nr; i++)
>  		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
>
> --
> 2.55.0.1074.ge7621b4bad.dirty
Karthik NayakSep 10, 2026, 10:54 UTC in reply to Karthik Nayak on lore

Re: [PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones

Karthik Nayak <karthik.188@gmail.com> writes:
Show 49 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
>
>> When cloning a local repository with "--shared" we add that repository
>> to the new repository's alternates. This is done in `clone_local()`,
>> which is responsible for performing local clones.
>>
>> Move the logic into `collect_alternates()` to unify our setup of
>> alternates. Furthermore, this will allow us to set up alternates right
>> at creation time of the object database.
>>
>> Note that the logic for cloning a local repository with "--no-shared" is
>> not yet part of `collect_alternates()`. This will be handled in the next
>> commit.
>>
>> Signed-off-by: Patrick Steinhardt <ps@pks.im>
>> ---
>>  builtin/clone.c | 23 ++++++++++++++---------
>>  1 file changed, 14 insertions(+), 9 deletions(-)
>>
>> diff --git a/builtin/clone.c b/builtin/clone.c
>> index 8786a49332..011fc867c8 100644
>> --- a/builtin/clone.c
>> +++ b/builtin/clone.c
>> @@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
>>  	fclose(in);
>>  }
>>
>> -static void collect_alternates(struct strvec *alternates)
>> +static void collect_alternates(struct strvec *alternates,
>> +			       const char *src_repo, bool is_local)
>>  {
>>  	if (option_required_reference.nr || option_optional_reference.nr) {
>>  		struct add_one_alternate_data data = {
>> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
>>  		for_each_string_list(&option_optional_reference,
>>  				     add_one_alternate, &data);
>>  	}
>> +
>> +	if (is_local) {
>>
>
> Shouldn't we also check for `option_shared` here?
>
>> +		struct strbuf commondir = STRBUF_INIT;
>> +
>> +		get_common_dir(&commondir, src_repo);
>> +		if (option_shared)
>> +			strvec_pushf(alternates, "%s/objects", commondir.buf);
>> +
So we do it here, but then commondir is initiated but unused otherwise.
>> +		strbuf_release(&commondir);
>> +	}
>>  }
>>
[snip]
Karthik NayakSep 10, 2026, 11:00 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v4 6/9] builtin/clone: move setup of alternates for non-shared local clones

Patrick Steinhardt <ps@pks.im> writes:
Show 14 quoted lines
> Similar as in the preceding commit, move the setup of alternates for
> local clones with "--no-shared" into `collect_alternates()`. With this
> step, the complete setup of alternates is now handled by that function.
>
> Note that besides moving stuff around, it also fixes a bug: previously,
> we did not know to resolve the referenced repository's common directory.
> Consequently, when referencing a worktree we failed to resolve
> alternates. But as `collect_alternates()` already knows to resolve the
> commondir for "--local" we can simply reuse this resolved path for our
> purpose.
>
> Add two tests, the first one of which exercises this bug to avoid future
> regressions. The second test ensures that we properly handle relative
> alternates for a referenced worktree.
[snip]
Show 8 quoted lines
>  static void collect_alternates(struct strvec *alternates,
> @@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,
>  		get_common_dir(&commondir, src_repo);
>  		if (option_shared)
>  			strvec_pushf(alternates, "%s/objects", commondir.buf);
> +		else
> +			read_alternates(alternates, commondir.buf);
>
Okay so this is why we did what we did in the prev patch. Makes sense.
>  		strbuf_release(&commondir);
>  	}
[snip]
Karthik NayakSep 10, 2026, 11:10 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v4 7/9] odb/source: support writing alternates when creating the database

Patrick Steinhardt <ps@pks.im> writes:
Show 46 quoted lines
> Add the ability to write alternates when creating the object database.
> This change allows us to remove the `write_alternates()` callback in a
> subsequent patch.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
>  odb/source.h       | 17 +++++++++---
>  setup.c            |  4 ++-
>  3 files changed, 91 insertions(+), 6 deletions(-)
>
> diff --git a/odb/source-files.c b/odb/source-files.c
> index b7b3a297bb..8fe65d91f8 100644
> --- a/odb/source-files.c
> +++ b/odb/source-files.c
> @@ -18,6 +18,7 @@
>  #include "run-command.h"
>  #include "strbuf.h"
>  #include "string-list.h"
> +#include "strmap.h"
>  #include "strvec.h"
>  #include "tree.h"
>  #include "write-or-die.h"
> @@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)
>  	odb_source_close(&files->packed->base);
>  }
>
> -static int odb_source_files_create_on_disk(struct odb_source *source)
> +static int odb_source_files_create_on_disk(struct odb_source *source,
> +					   const struct odb_create_on_disk_options *opts)
>  {
> +	struct lock_file alternates_lock = LOCK_INIT;
>  	struct strbuf path = STRBUF_INIT;
> +	struct strset seen = STRSET_INIT;
> +	struct strbuf line = STRBUF_INIT;
> +	int ret;
>
>  	safe_create_dir(source->odb->repo, source->path, 1);
>
> @@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
>  	strbuf_addf(&path, "%s/info", source->path);
>  	safe_create_dir(source->odb->repo, path.buf, 1);
>
> +	if (opts->alternates && opts->alternates->nr) {
> +		FILE *alternates, *orig;
> +

So this is similar to what we already do in `odb_source_files_write_alternate()`.

Show 29 quoted lines
> +		strbuf_reset(&path);
> +		strbuf_addf(&path, "%s/info/alternates", source->path);
> +
> +		repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,
> +					       path.buf, LOCK_DIE_ON_ERROR);
> +
> +		alternates = fdopen_lock_file(&alternates_lock, "w");
> +		if (!alternates) {
> +			ret = error_errno(_("unable to fdopen alternates lockfile"));
> +			goto out;
> +		}
> +
> +		/*
> +		 * The alternates file may already exist, e.g. when it has been
> +		 * seeded from a template directory. Read any preexisting
> +		 * entries so that we don't end up writing duplicates.
> +		 */
> +		orig = fopen(path.buf, "r");
> +		if (orig) {
> +			while (strbuf_getline(&line, orig) != EOF) {
> +				strset_add(&seen, line.buf);
> +				fprintf(alternates, "%s\n", line.buf);
> +			}
> +
> +			if (ferror(orig)) {
> +				ret = error_errno(_("unable to read alternates file"));
> +				fclose(orig);
> +				goto out;
> +			}
Shouldn't this be checked inside the for loop with every `fprintf` call?
Show 19 quoted lines
> +
> +			fclose(orig);
> +		} else if (errno != ENOENT) {
> +			ret = error_errno(_("unable to read alternates file"));
> +			goto out;
> +		}
> +
> +		for (size_t i = 0; i < opts->alternates->nr; i++) {
> +			const char *alternate = opts->alternates->v[i];
> +			if (!strset_add(&seen, alternate))
> +				continue;
> +			fprintf(alternates, "%s\n", alternate);
> +		}
> +
> +		if (ferror(alternates)) {
> +			ret = error_errno(_("unable to write alternates file"));
> +			goto out;
> +		}
> +
same here.
Show 35 quoted lines
> +		if (commit_lock_file(&alternates_lock)) {
> +			ret = error_errno(_("unable to commit alternates file"));
> +			goto out;
> +		}
> +	}
> +
> +	/* Reprepare the object database to activate alternates. */
> +	odb_reprepare(source->odb);
> +
> +	ret = 0;
> +
> +out:
> +	rollback_lock_file(&alternates_lock);
> +	strbuf_release(&line);
>  	strbuf_release(&path);
> -	return 0;
> +	strset_clear(&seen);
> +	return ret;
>  }
>
>  static void odb_source_files_prepare(struct odb_source *source,
> diff --git a/odb/source.h b/odb/source.h
> index ea8675247e..63f1c0c531 100644
> --- a/odb/source.h
> +++ b/odb/source.h
> @@ -36,6 +36,15 @@ struct object_id;
>  struct odb_stream;
>  struct strvec;
>
> +struct odb_create_on_disk_options {
> +	/*
> +	 * Alternates that shall be written into the newly created object
> +	 * database. Whether or not this option can be handled is specific to
> +	 * the backend.
> +	 */

Would it make sense to formalize errors thrown by backends, so we know when a backend specifically cannot handle alternates?

> +	const struct strvec *alternates;
> +};
> +
[snip]
Patrick SteinhardtSep 10, 2026, 14:26 UTC in reply to Karthik Nayak on lore

Re: [PATCH v4 4/9] builtin/clone: refactor handling of "--reference{,-if-able}"

On Thu, Sep 10, 2026 at 02:37:31AM -0700, Karthik Nayak wrote:
Show 15 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/builtin/clone.c b/builtin/clone.c
> > index ac5843d7b9..8786a49332 100644
> > --- a/builtin/clone.c
> > +++ b/builtin/clone.c
> > @@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
> >  	return canon;
> >  }
> >
> > -static int add_one_reference(struct string_list_item *item, void *cb_data)
> > +struct add_one_alternate_data {
> 
> So this is `add_one_alternate()`'s data, was a bit confusing cause I
> first read that this was information regarding a single alternate, but
> then it also has a field called `alternates`.

We can also rename this to `collect_one_alternate()` and the structure `collect_alternates_data`.

Show 5 quoted lines
> > +	struct strvec *alternates;
> > +	int required;
> 
> Question: here and other places, I see some of the boolean-ish fields
> being declared as `int`. It doesn't matter, but is there a reason?

No, there isn't. I'm probably just still getting used to new fancy features like that.

Show 32 quoted lines
> > @@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
> >  	fclose(in);
> >  }
> >
> > -static void setup_reference(void)
> > +static void collect_alternates(struct strvec *alternates)
> >  {
> > -	int required = 1;
> > -	for_each_string_list(&option_required_reference,
> > -			     add_one_reference, &required);
> > -	required = 0;
> > -	for_each_string_list(&option_optional_reference,
> > -			     add_one_reference, &required);
> > +	if (option_required_reference.nr || option_optional_reference.nr) {
> > +		struct add_one_alternate_data data = {
> > +			.alternates = alternates,
> > +			.required = 1,
> > +		};
> > +
> > +		for_each_string_list(&option_required_reference,
> > +				     add_one_alternate, &data);
> > +		data.required = 0;
> > +		for_each_string_list(&option_optional_reference,
> > +				     add_one_alternate, &data);
> > +	}
> 
> Nit: might be nicer to read
> 
>      if (!option_required_reference.nr && !option_optional_reference.nr)
>         return;
> 
> The rest of it doesn't have to be in the `if` block.

We'll extend this over the next couple patches, so I'll leave this as-is.

Show 14 quoted lines
> > @@ -1339,8 +1348,10 @@ int cmd_clone(int argc,
> >  		warning(_("--local is ignored"));
> >
> >  	create_object_database(the_repository);
> > -	if (option_required_reference.nr || option_optional_reference.nr)
> > -		setup_reference();
> > +	collect_alternates(&alternates);
> > +
> > +	for (size_t i = 0; i < alternates.nr; i++)
> > +		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
> >
> 
> So now adding the alternates is moved out of `collect_alternates()`.
> Nit: might be nice to mention this in the commit message.
Will do.
Patrick
Patrick SteinhardtSep 10, 2026, 14:26 UTC in reply to Karthik Nayak on lore

Re: [PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones

On Thu, Sep 10, 2026 at 05:54:50AM -0500, Karthik Nayak wrote:
Show 24 quoted lines
> Karthik Nayak <karthik.188@gmail.com> writes:
> > Patrick Steinhardt <ps@pks.im> writes:
> >> diff --git a/builtin/clone.c b/builtin/clone.c
> >> index 8786a49332..011fc867c8 100644
> >> --- a/builtin/clone.c
> >> +++ b/builtin/clone.c
> >> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
> >>  		for_each_string_list(&option_optional_reference,
> >>  				     add_one_alternate, &data);
> >>  	}
> >> +
> >> +	if (is_local) {
> >>
> >
> > Shouldn't we also check for `option_shared` here?
> >
> >> +		struct strbuf commondir = STRBUF_INIT;
> >> +
> >> +		get_common_dir(&commondir, src_repo);
> >> +		if (option_shared)
> >> +			strvec_pushf(alternates, "%s/objects", commondir.buf);
> >> +
> 
> So we do it here, but then commondir is initiated but unused otherwise.

It is, yes. But this is done to prep for the next step, where the `commondir` variable will be used regardless of whether or not we use `option_shared`.

I'll adapt the commit message a bit.
Patrick
Patrick SteinhardtSep 10, 2026, 15:09 UTC in reply to Patrick Steinhardt on lore

[PATCH v5 0/9] odb: write alternates at creation time

Hi,

writing alternates into the object database currently happens via `odb_source_write_alternate()`. But while that creates the ability to create alternates at arbitrary points of a source's lifetime, we don't use that functionality in the first place. Instead, we only ever write alternates when creating a new repository.

This design is suboptimal due to a couple of reasons:
  - It requires us to have a `write_alternates()` callback, which is
    overblown as we never even write alternates to an object database
    after it has been created.
  - We're about to make alternates an implementation detail of the
    object database's backend in a future patch series, so alternate
    implementations may not even support them.
  - The backend has more flexibility with how exactly alternates are
    configured when it itself is in full control over their setup at the
    time where it creates the object database itself.

This patch series thus refactors how we handle alternates so that we don't write them ad-hoc anymore. Instead, the series introduces a new option for `odb_source_create_on_disk()` that makes it handle those alternates at creation time.

This is part of the bigger goal of moving handling of alternates into the "files" backend.

This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24) with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop `alternates_db` field, 2026-08-17) merged into it.

Changes in v5:
  - Rename `add_one_alternate` and `add_one_alternate_data` to
    `collect_one_alternate` and `collect_alternates_data` to clarify
    their intent a bit.
  - Drop extra parameter in `collect_alternates()`.
  - Clarify why we compute `commondir` even though it's unused.
  - Link to v4: https://patch.msgid.link/20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im
Changes in v4:
  - Add documentation for the different functions that play a role in
    creating repositories.
  - Link to v3: https://patch.msgid.link/20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im
Changes in v3:
  - Refactor `init_db()` to not create the reference and object database
    at all anymore. Instead, it's now called `create_repository()` and
    it is responsible for creating the initial repository skeleton,
    only. This allows us to get rid of the flags and overall makes the
    logic more straight-forward by moving the command-specific logic
    into the respective commands.
  - A couple of typo fixes.
  - Link to v2: https://patch.msgid.link/20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im
Changes in v2:
  - Use a lockfile to write "info/alternates" during creation time.
  - Remove useless "strvec.h" include by reordering declarations a bit.
  - Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im
Thanks!
Patrick
---
Patrick Steinhardt (9):
      setup: split up concerns of `init_db()`
      builtin/clone: defer setup of the object database
      builtin/clone: move around `setup_reference()`
      builtin/clone: refactor handling of "--reference{,-if-able}"
      builtin/clone: move setup of alternates for shared local clones
      builtin/clone: move setup of alternates for non-shared local clones
      odb/source: support writing alternates when creating the database
      builtin/clone: write alternates via `odb_create_on_disk()`
      odb/source: remove the ability to write alternates
 builtin/clone.c            | 111 +++++++++++++++++++++++---------------
 builtin/init-db.c          |  32 ++++++++---
 odb.c                      |   9 ----
 odb.h                      |   7 ---
 odb/source-files.c         | 130 ++++++++++++++++++++++++++-------------------
 odb/source-inmemory.c      |   7 ---
 odb/source-loose.c         |   7 ---
 odb/source-packed.c        |   7 ---
 odb/source.h               |  43 +++++----------
 setup.c                    |  61 ++++++++-------------
 setup.h                    |  47 +++++++++++-----
 t/t5604-clone-reference.sh |  25 +++++++++
 12 files changed, 264 insertions(+), 222 deletions(-)
Range-diff versus v4:
 1:  30a1faa807 =  1:  e5d789fdfa setup: split up concerns of `init_db()`
 2:  7a646ebe92 !  2:  94a19390b4 builtin/clone: defer setup of the object database
    @@ Commit message
         namely alternates. When initializing the object database we do not yet
         know whether we'll need alternates or not because this depends on the
         repository we're about to clone from. If it is a local repository and
    -    the user has passed "--refernce{,-if-able}", then we will end up writing
    -    alternates into the object database.
    +    the user has passed "--reference{,-if-able}", then we will end up
    +    writing alternates into the object database.
     
         The ugly part though is that we cannot determine where the repository is
         getting cloned from before it has been initialized. While we of course
 3:  af77d0e9e3 =  3:  e9ccb5b1bd builtin/clone: move around `setup_reference()`
 4:  2ca937bce1 !  4:  30c4933fb0 builtin/clone: refactor handling of "--reference{,-if-able}"
    @@ Commit message
         the alternates into it one by one.
     
         Prepare for this refactoring by collecting the alternates into a strvec.
    -    Rename the function to `collect_alternates()` to clarify its scope.
    +    Rename the function to `collect_alternates()` to clarify its scope, as
    +    it does not set up the references itself anymore.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
    @@ builtin/clone.c: static char *get_repo_path(const char *repo, int *is_bundle)
      }
      
     -static int add_one_reference(struct string_list_item *item, void *cb_data)
    -+struct add_one_alternate_data {
    ++struct collect_alternates_data {
     +	struct strvec *alternates;
    -+	int required;
    ++	bool required;
     +};
     +
    -+static int add_one_alternate(struct string_list_item *item, void *cb_data)
    ++static int collect_one_alternate(struct string_list_item *item, void *cb_data)
      {
    -+	struct add_one_alternate_data *data = cb_data;
    ++	struct collect_alternates_data *data = cb_data;
      	struct strbuf err = STRBUF_INIT;
     -	int *required = cb_data;
      	char *ref_git = compute_alternate_path(item->string, &err);
    @@ builtin/clone.c: static void copy_alternates(struct strbuf *src, const char *src
     -	for_each_string_list(&option_optional_reference,
     -			     add_one_reference, &required);
     +	if (option_required_reference.nr || option_optional_reference.nr) {
    -+		struct add_one_alternate_data data = {
    ++		struct collect_alternates_data data = {
     +			.alternates = alternates,
    -+			.required = 1,
    ++			.required = true,
     +		};
     +
     +		for_each_string_list(&option_required_reference,
    -+				     add_one_alternate, &data);
    -+		data.required = 0;
    ++				     collect_one_alternate, &data);
    ++		data.required = false;
     +		for_each_string_list(&option_optional_reference,
    -+				     add_one_alternate, &data);
    ++				     collect_one_alternate, &data);
     +	}
      }
      
 5:  4ba60fc24b !  5:  5141c179e2 builtin/clone: move setup of alternates for shared local clones
    @@ Commit message
     
         Note that the logic for cloning a local repository with "--no-shared" is
         not yet part of `collect_alternates()`. This will be handled in the next
    -    commit.
    +    commit, but means that at this step, we may compute `commondir` without
    +    it being used. It will become used in the next step though.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
    @@ builtin/clone.c: static void copy_alternates(struct strbuf *src, const char *src
      
     -static void collect_alternates(struct strvec *alternates)
     +static void collect_alternates(struct strvec *alternates,
    -+			       const char *src_repo, bool is_local)
    ++			       const char *local_source_repo)
      {
      	if (option_required_reference.nr || option_optional_reference.nr) {
    - 		struct add_one_alternate_data data = {
    + 		struct collect_alternates_data data = {
     @@ builtin/clone.c: static void collect_alternates(struct strvec *alternates)
      		for_each_string_list(&option_optional_reference,
    - 				     add_one_alternate, &data);
    + 				     collect_one_alternate, &data);
      	}
     +
    -+	if (is_local) {
    ++	if (local_source_repo) {
     +		struct strbuf commondir = STRBUF_INIT;
     +
    -+		get_common_dir(&commondir, src_repo);
    ++		get_common_dir(&commondir, local_source_repo);
     +		if (option_shared)
     +			strvec_pushf(alternates, "%s/objects", commondir.buf);
     +
    @@ builtin/clone.c: int cmd_clone(int argc,
      
      	create_object_database(the_repository);
     -	collect_alternates(&alternates);
    -+	collect_alternates(&alternates, path, is_local);
    ++	collect_alternates(&alternates, is_local ? path : NULL);
      
      	for (size_t i = 0; i < alternates.nr; i++)
      		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
 6:  4f3c8fb936 !  6:  b0ed987d1c builtin/clone: move setup of alternates for non-shared local clones
    @@ Commit message
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
      ## builtin/clone.c ##
    -@@ builtin/clone.c: static int add_one_alternate(struct string_list_item *item, void *cb_data)
    +@@ builtin/clone.c: static int collect_one_alternate(struct string_list_item *item, void *cb_data)
      	return 0;
      }
      
    @@ builtin/clone.c: static void copy_alternates(struct strbuf *src, const char *src
      
      static void collect_alternates(struct strvec *alternates,
     @@ builtin/clone.c: static void collect_alternates(struct strvec *alternates,
    - 		get_common_dir(&commondir, src_repo);
    + 		get_common_dir(&commondir, local_source_repo);
      		if (option_shared)
      			strvec_pushf(alternates, "%s/objects", commondir.buf);
     +		else
 7:  47548ac464 =  7:  4cc70ca1dd odb/source: support writing alternates when creating the database
 8:  4e3be623dd !  8:  2cea0312bd builtin/clone: write alternates via `odb_create_on_disk()`
    @@ builtin/clone.c: int cmd_clone(int argc,
      		warning(_("--local is ignored"));
      
     -	create_object_database(the_repository);
    - 	collect_alternates(&alternates, path, is_local);
    + 	collect_alternates(&alternates, is_local ? path : NULL);
     -
     -	for (size_t i = 0; i < alternates.nr; i++)
     -		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
 9:  7207b7e2ed =  9:  e1f8f728a6 odb/source: remove the ability to write alternates

--- base-commit: afa255aeb620346d56a2c01fb5ae9163513c56d7 change-id: 20260813-pks-odb-write-alternates-at-creation-time-64010deb94a0

Patrick SteinhardtSep 10, 2026, 15:09 UTC in reply to Patrick Steinhardt on lore

[PATCH v5 1/9] setup: split up concerns of `init_db()`

The function `init_db()` is responsible for creating the on-disk directory structure required for a Git repository. It is used by both git-init(1) and git-clone(1), and because their expected behaviour is different we support a couple of flags:

  - The `QUIET` flag controls whether the command is quiet or not. For
    git-init(1) this is user-controllable, whereas for git-clone(1)
    we're always quiet.
  - The `EXIST_OK` flag controls whether a preexisting repository is
    okay or not. For git-init(1) it is, for git-clone(1) it's not.
  - The `SKIP_REFDB` flag controls whether the reference database should
    already be created or not. For git-init(1) we do, but for
    git-clone(1) we don't because it does not yet know about the default
    branch and about the remote object hash.

Furthermore, we're about to add another divergence in behaviour, where we have to also skip creation of the object database in git-clone(1). This is becoming quite cumbersome though.

Instead of introducing another flag, start to split up concerns of the function so that we never create the reference or object database. This becomes the responsibility of the caller, which is thus free to defer their creation to a later point in time. This lets us get rid of most of the divergent behaviour:

  - We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags
    anymore.
  - We don't need the `QUIET` flag anymore, as nothing prints output
    except for the final status message that tells the user that the
    repository has been (re)initialized. But as this message is specific
    to git-init(1), we can easily move it there.

The only piece of information we still have to convey is whether or not reinitialization of a preexisting repository is okay. This is handled by a new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay to reinitialize the repository. Furthermore, the pointer will be written to to indicate whether the repository was reinitialized or not, which we need in git-init(1) to print the correct initialization message.

With these refactorings, `init_db()` is named quite misleadingly though, as we don't create any of the reference or object databases anymore. Rename it to `create_repository()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c   |  9 +++++----
 builtin/init-db.c | 32 ++++++++++++++++++++++++--------
 setup.c           | 54 +++++++++++++++++-------------------------------------
 setup.h           | 45 +++++++++++++++++++++++++++++++++------------
 4 files changed, 79 insertions(+), 61 deletions(-)
Show changes to 4 files +79 −61

builtin/clone.c, builtin/init-db.c, setup.c, setup.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 5b25cca510..904d2d859f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1185,10 +1185,10 @@ int cmd_clone(int argc,
 	 * repository, and reference backends may persist that information into
 	 * their on-disk data structures.
 	 */
-	init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
-		GIT_HASH_UNKNOWN, ref_storage_format, NULL,
-		do_not_override_repo_unix_permissions,
-		INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
+	create_repository(the_repository, git_dir, real_git_dir, work_tree,
+			  option_template, GIT_HASH_UNKNOWN, ref_storage_format,
+			  do_not_override_repo_unix_permissions, NULL);
+	create_object_database(the_repository);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
@@ -1445,6 +1445,7 @@ int cmd_clone(int argc,
 	initialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);
 	repo_set_hash_algo(the_repository, hash_algo);
 	create_reference_database(the_repository, NULL, 1);
+	startup_info->have_repository = 1;
 
 	/*
 	 * Before fetching from the remote, download and install bundle
diff --git a/builtin/init-db.c b/builtin/init-db.c
index e96b1283b7..f2c7e3be6d 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -80,7 +80,7 @@ int cmd_init_db(int argc,
 	char *work_tree = NULL;
 	const char *template_dir = NULL;
 	char *template_dir_to_free = NULL;
-	unsigned int flags = 0;
+	int quiet = 0;
 	int bare = startup_info->force_bare_repository ? 1 : -1;
 	const char *object_format = NULL;
 	const char *ref_format = NULL;
@@ -102,7 +102,7 @@ int cmd_init_db(int argc,
 			.flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
 			.callback = shared_callback
 		},
-		OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
+		OPT_BOOL('q', "quiet", &quiet, N_("be quiet")),
 		OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
 			   N_("separate git dir from working tree")),
 		OPT_STRING('b', "initial-branch", &initial_branch, N_("name"),
@@ -113,7 +113,7 @@ int cmd_init_db(int argc,
 			   N_("specify the reference format to use")),
 		OPT_END()
 	};
-	int ret;
+	int reinit;
 
 	argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
 
@@ -247,14 +247,30 @@ int cmd_init_db(int argc,
 		die(_("--separate-git-dir incompatible with bare repository"));
 	}
 
-	flags |= INIT_DB_EXIST_OK;
-	ret = init_db(the_repository, git_dir, real_git_dir, work_tree,
-		      template_dir, hash_algo, ref_storage_format, initial_branch,
-		      init_shared_repository, flags);
+	create_repository(the_repository, git_dir, real_git_dir, work_tree,
+			  template_dir, hash_algo, ref_storage_format,
+			  init_shared_repository, &reinit);
+	create_reference_database(the_repository, initial_branch, quiet);
+	create_object_database(the_repository);
+
+	if (!quiet) {
+		int len = strlen(git_dir);
+
+		if (reinit)
+			printf(repo_settings_get_shared_repository(the_repository)
+			       ? _("Reinitialized existing shared Git repository in %s%s\n")
+			       : _("Reinitialized existing Git repository in %s%s\n"),
+			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+		else
+			printf(repo_settings_get_shared_repository(the_repository)
+			       ? _("Initialized empty shared Git repository in %s%s\n")
+			       : _("Initialized empty Git repository in %s%s\n"),
+			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+	}
 
 	free(template_dir_to_free);
 	free(real_git_dir_to_free);
 	free(work_tree);
 	free(git_dir);
-	return ret;
+	return 0;
 }
diff --git a/setup.c b/setup.c
index d90654f584..8c7b97f92e 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,
 	return reinit;
 }
 
-static void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo)
 {
 	/*
 	 * Create the "objects" directory in the common directory. This is done
@@ -2822,17 +2822,17 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 	}
 }
 
-int init_db(struct repository *repo,
-	    const char *git_dir,
-	    const char *real_git_dir,
-	    const char *worktree,
-	    const char *template_dir, int hash,
-	    enum ref_storage_format ref_storage_format,
-	    const char *initial_branch,
-	    int init_shared_repository, unsigned int flags)
+void create_repository(struct repository *repo,
+		       const char *git_dir,
+		       const char *real_git_dir,
+		       const char *worktree,
+		       const char *template_dir,
+		       int hash,
+		       enum ref_storage_format ref_storage_format,
+		       int init_shared_repository,
+		       int *reinit_ok)
 {
-	int reinit;
-	int exist_ok = flags & INIT_DB_EXIST_OK;
+	int reinit_ignored;
 	char *original_git_dir = real_pathdup(git_dir, 1);
 	struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
 	struct strbuf err = STRBUF_INIT;
@@ -2840,10 +2840,10 @@ int init_db(struct repository *repo,
 	if (real_git_dir) {
 		struct stat st;
 
-		if (!exist_ok && !stat(git_dir, &st))
+		if (!reinit_ok && !stat(git_dir, &st))
 			die(_("%s already exists"), git_dir);
 
-		if (!exist_ok && !stat(real_git_dir, &st))
+		if (!reinit_ok && !stat(real_git_dir, &st))
 			die(_("%s already exists"), real_git_dir);
 
 		apply_and_export_relative_gitdir(repo, real_git_dir, 1);
@@ -2877,8 +2877,10 @@ int init_db(struct repository *repo,
 
 	safe_create_dir(repo, git_dir, 0);
 
-	reinit = create_default_files(repo, template_dir, original_git_dir,
-				      &repo_fmt, init_shared_repository);
+	if (!reinit_ok)
+		reinit_ok = &reinit_ignored;
+	*reinit_ok = create_default_files(repo, template_dir, original_git_dir,
+					  &repo_fmt, init_shared_repository);
 
 	if (repo_settings_get_shared_repository(repo)) {
 		char buf[10];
@@ -2901,29 +2903,7 @@ int init_db(struct repository *repo,
 		repo_config_set(repo, "receive.denyNonFastforwards", "true");
 	}
 
-	if (!(flags & INIT_DB_SKIP_REFDB))
-		create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
-	create_object_database(repo);
-
-	startup_info->have_repository = 1;
-
-	if (!(flags & INIT_DB_QUIET)) {
-		int len = strlen(git_dir);
-
-		if (reinit)
-			printf(repo_settings_get_shared_repository(repo)
-			       ? _("Reinitialized existing shared Git repository in %s%s\n")
-			       : _("Reinitialized existing Git repository in %s%s\n"),
-			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
-		else
-			printf(repo_settings_get_shared_repository(repo)
-			       ? _("Initialized empty shared Git repository in %s%s\n")
-			       : _("Initialized empty Git repository in %s%s\n"),
-			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
-	}
-
 	clear_repository_format(&repo_fmt);
 	strbuf_release(&err);
 	free(original_git_dir);
-	return 0;
 }
diff --git a/setup.h b/setup.h
index 763fd384e8..f1c1ed65fb 100644
--- a/setup.h
+++ b/setup.h
@@ -256,24 +256,45 @@ int apply_repository_format(struct repository *repo,
 
 const char *get_template_dir(const char *option_template);
 
-#define INIT_DB_QUIET      (1 << 0)
-#define INIT_DB_EXIST_OK   (1 << 1)
-#define INIT_DB_SKIP_REFDB (1 << 2)
-
-int init_db(struct repository *repo,
-	    const char *git_dir,
-	    const char *real_git_dir,
-	    const char *worktree,
-	    const char *template_dir, int hash_algo,
-	    enum ref_storage_format ref_storage_format,
-	    const char *initial_branch, int init_shared_repository,
-	    unsigned int flags);
+/*
+ * Create the repository by creating the necessary directory structures,
+ * setting up the configuration and configuring the repository's format. If
+ * `template_dir` is set, copy over templates from that directory. Furthermore,
+ * if and only if `reinit_ok` is a non-NULL pointer, then the function may
+ * reinitialize a preexisting repository. In that case, the pointer will be set
+ * to `1` in case the repo was reinitialized and `0` if it didn't exist yet.
+ *
+ * Note that this function does not create the reference and object databases.
+ */
+void create_repository(struct repository *repo,
+		       const char *git_dir,
+		       const char *real_git_dir,
+		       const char *worktree,
+		       const char *template_dir,
+		       int hash_algo,
+		       enum ref_storage_format ref_storage_format,
+		       int init_shared_repository,
+		       int *reinit_ok);
+
 void initialize_repository_version(struct repository *repo,
 				   int hash_algo,
 				   enum ref_storage_format ref_storage_format,
 				   int reinit);
+
+/*
+ * Create the reference database for the repository. The repository and its ref
+ * storage format must have already been configured properly before calling
+ * this function. When set, `initial_branch` overrides the default branch that
+ * HEAD will point to.
+ */
 void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
 
+/*
+ * Create the object database for the repository. The repository must have
+ * already been configured properly before calling this function.
+ */
+void create_object_database(struct repository *repo);
+
 /*
  * NOTE NOTE NOTE!!
  *
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 10, 2026, 15:09 UTC in reply to Patrick Steinhardt on lore

[PATCH v5 2/9] builtin/clone: defer setup of the object database

When cloning a repository we defer initialization of the reference database. This is because we don't yet know all details required for us to initialize the refdb in the first place. Most importantly, what we are missing is information about the object hash.

We don't do the same thing for the object database yet, but here we essentially have the same problem. While the "files" database does not need any information about the object format at creation time, alternate backends are likely to require that information so that they can properly set up their data structures.

Besides this forward-looking future proofing though, we also have a second use case for deferring initialization of the object database, namely alternates. When initializing the object database we do not yet know whether we'll need alternates or not because this depends on the repository we're about to clone from. If it is a local repository and the user has passed "--reference{,-if-able}", then we will end up writing alternates into the object database.

The ugly part though is that we cannot determine where the repository is getting cloned from before it has been initialized. While we of course already have access to the user-provided URI, that URI can be very well rewritten via "url.<base>.insteadOf". We can of course read the global- and system-level configuration to resolve it. But we explicitly resolve the URI a second time after we have initialized the repository because it can happen that we copy a ".git/config" over from our templates, and that file may cause us to rewrite the path.

In a subsequent commit though we'll start to write alternates as part of the repository initialization, so we'll need to have the URI properly resolved before we can initialize the object database. This is ugly, but as mentioned above it makes sense for us to defer its initialization anyway so that we also know about the object hash already.

Defer creation of the object database until after we have resolved the URI.

Note that this also requires us to defer the call to `setup_reference()` until after we have created the object database. While you might think that this function has something to do with references ("refs/*"), it is in fact responsible for setting up alternates. Consequently, we can only call it after we have created the object database already.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
Show changes to builtin/clone.c +4 −4
diff --git a/builtin/clone.c b/builtin/clone.c
index 904d2d859f..bdcbd7aa1b 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1188,7 +1188,6 @@ int cmd_clone(int argc,
 	create_repository(the_repository, git_dir, real_git_dir, work_tree,
 			  option_template, GIT_HASH_UNKNOWN, ref_storage_format,
 			  do_not_override_repo_unix_permissions, NULL);
-	create_object_database(the_repository);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
@@ -1311,9 +1310,6 @@ int cmd_clone(int argc,
 		strbuf_reset(&key);
 	}
 
-	if (option_required_reference.nr || option_optional_reference.nr)
-		setup_reference();
-
 	remote = remote_get_early(remote_name);
 
 	if (!option_rev)
@@ -1342,6 +1338,10 @@ int cmd_clone(int argc,
 	if (option_local > 0 && !is_local)
 		warning(_("--local is ignored"));
 
+	create_object_database(the_repository);
+	if (option_required_reference.nr || option_optional_reference.nr)
+		setup_reference();
+
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
 	transport->family = family;
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 10, 2026, 15:09 UTC in reply to Patrick Steinhardt on lore

[PATCH v5 3/9] builtin/clone: move around `setup_reference()`

In a subsequent commit, `setup_reference()` will start to call `copy_alternates()`. Prepare for this by moving the function further down so that we can avoid adding a declaration.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)
Show changes to builtin/clone.c +10 −10
diff --git a/builtin/clone.c b/builtin/clone.c
index bdcbd7aa1b..ac5843d7b9 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
 	return 0;
 }
 
-static void setup_reference(void)
-{
-	int required = 1;
-	for_each_string_list(&option_required_reference,
-			     add_one_reference, &required);
-	required = 0;
-	for_each_string_list(&option_optional_reference,
-			     add_one_reference, &required);
-}
-
 static void copy_alternates(struct strbuf *src, const char *src_repo)
 {
 	/*
@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
+static void setup_reference(void)
+{
+	int required = 1;
+	for_each_string_list(&option_required_reference,
+			     add_one_reference, &required);
+	required = 0;
+	for_each_string_list(&option_optional_reference,
+			     add_one_reference, &required);
+}
+
 static void mkdir_if_missing(const char *pathname, mode_t mode)
 {
 	struct stat st;
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 10, 2026, 15:09 UTC in reply to Patrick Steinhardt on lore

[PATCH v5 4/9] builtin/clone: refactor handling of "--reference{,-if-able}"

Users can pass "--reference{,-if-able}" to git-clone(1) to instruct it to set up alternates for the newly created repository. This allows it to reuse objects from the source repository so that in the best case we don't have to clone all objects over.

Those options are handled by the confusingly named `setup_reference()` function -- without the above context, one might rightfully believe that it was about refs, not about alternates. The function itself is rather simple: we loop through all provided alternate paths and then, if such an alternate is valid, we write it to the object database.

In subsequent commits we're about to consolidate the complete setup of alternates into this function, and furthermore we'll refactor the setup of the object database to handle doing this for us instead of writing the alternates into it one by one.

Prepare for this refactoring by collecting the alternates into a strvec. Rename the function to `collect_alternates()` to clarify its scope, as it does not set up the references itself anymore.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------
 1 file changed, 28 insertions(+), 16 deletions(-)
Show changes to builtin/clone.c +28 −16
diff --git a/builtin/clone.c b/builtin/clone.c
index ac5843d7b9..08d913d306 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
 	return canon;
 }
 
-static int add_one_reference(struct string_list_item *item, void *cb_data)
+struct collect_alternates_data {
+	struct strvec *alternates;
+	bool required;
+};
+
+static int collect_one_alternate(struct string_list_item *item, void *cb_data)
 {
+	struct collect_alternates_data *data = cb_data;
 	struct strbuf err = STRBUF_INIT;
-	int *required = cb_data;
 	char *ref_git = compute_alternate_path(item->string, &err);
 
 	if (!ref_git) {
-		if (*required)
+		if (data->required)
 			die("%s", err.buf);
 		else
 			fprintf(stderr,
 				_("info: Could not add alternate for '%s': %s\n"),
 				item->string, err.buf);
 	} else {
-		struct strbuf sb = STRBUF_INIT;
-		strbuf_addf(&sb, "%s/objects", ref_git);
-		odb_add_to_alternates_file(the_repository->objects, sb.buf);
-		strbuf_release(&sb);
+		strvec_pushf(data->alternates, "%s/objects", ref_git);
 	}
 
 	strbuf_release(&err);
@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
-static void setup_reference(void)
+static void collect_alternates(struct strvec *alternates)
 {
-	int required = 1;
-	for_each_string_list(&option_required_reference,
-			     add_one_reference, &required);
-	required = 0;
-	for_each_string_list(&option_optional_reference,
-			     add_one_reference, &required);
+	if (option_required_reference.nr || option_optional_reference.nr) {
+		struct collect_alternates_data data = {
+			.alternates = alternates,
+			.required = true,
+		};
+
+		for_each_string_list(&option_required_reference,
+				     collect_one_alternate, &data);
+		data.required = false;
+		for_each_string_list(&option_optional_reference,
+				     collect_one_alternate, &data);
+	}
 }
 
 static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -999,6 +1007,7 @@ int cmd_clone(int argc,
 			   N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
 		OPT_END()
 	};
+	struct strvec alternates = STRVEC_INIT;
 
 	const char * const builtin_clone_usage[] = {
 		N_("git clone [<options>] [--] <repo> [<dir>]"),
@@ -1339,8 +1348,10 @@ int cmd_clone(int argc,
 		warning(_("--local is ignored"));
 
 	create_object_database(the_repository);
-	if (option_required_reference.nr || option_optional_reference.nr)
-		setup_reference();
+	collect_alternates(&alternates);
+
+	for (size_t i = 0; i < alternates.nr; i++)
+		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
 
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
@@ -1638,6 +1649,7 @@ int cmd_clone(int argc,
 	string_list_clear(&option_not, 0);
 	string_list_clear(&option_config, 0);
 	string_list_clear(&server_options, 0);
+	strvec_clear(&alternates);
 
 	free(remote_name);
 	strbuf_release(&reflog_msg);
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 10, 2026, 15:09 UTC in reply to Patrick Steinhardt on lore

[PATCH v5 5/9] builtin/clone: move setup of alternates for shared local clones

When cloning a local repository with "--shared" we add that repository to the new repository's alternates. This is done in `clone_local()`, which is responsible for performing local clones.

Move the logic into `collect_alternates()` to unify our setup of alternates. Furthermore, this will allow us to set up alternates right at creation time of the object database.

Note that the logic for cloning a local repository with "--no-shared" is not yet part of `collect_alternates()`. This will be handled in the next commit, but means that at this step, we may compute `commondir` without it being used. It will become used in the next step though.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)
Show changes to builtin/clone.c +14 −9
diff --git a/builtin/clone.c b/builtin/clone.c
index 08d913d306..d397fd36b2 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	fclose(in);
 }
 
-static void collect_alternates(struct strvec *alternates)
+static void collect_alternates(struct strvec *alternates,
+			       const char *local_source_repo)
 {
 	if (option_required_reference.nr || option_optional_reference.nr) {
 		struct collect_alternates_data data = {
@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
 		for_each_string_list(&option_optional_reference,
 				     collect_one_alternate, &data);
 	}
+
+	if (local_source_repo) {
+		struct strbuf commondir = STRBUF_INIT;
+
+		get_common_dir(&commondir, local_source_repo);
+		if (option_shared)
+			strvec_pushf(alternates, "%s/objects", commondir.buf);
+
+		strbuf_release(&commondir);
+	}
 }
 
 static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 
 static void clone_local(const char *src_repo, const char *dest_repo)
 {
-	if (option_shared) {
-		struct strbuf alt = STRBUF_INIT;
-		get_common_dir(&alt, src_repo);
-		strbuf_addstr(&alt, "/objects");
-		odb_add_to_alternates_file(the_repository->objects, alt.buf);
-		strbuf_release(&alt);
-	} else {
+	if (!option_shared) {
 		struct strbuf src = STRBUF_INIT;
 		struct strbuf dest = STRBUF_INIT;
 		get_common_dir(&src, src_repo);
@@ -1348,7 +1353,7 @@ int cmd_clone(int argc,
 		warning(_("--local is ignored"));
 
 	create_object_database(the_repository);
-	collect_alternates(&alternates);
+	collect_alternates(&alternates, is_local ? path : NULL);
 
 	for (size_t i = 0; i < alternates.nr; i++)
 		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 10, 2026, 15:09 UTC in reply to Patrick Steinhardt on lore

[PATCH v5 6/9] builtin/clone: move setup of alternates for non-shared local clones

Similar as in the preceding commit, move the setup of alternates for local clones with "--no-shared" into `collect_alternates()`. With this step, the complete setup of alternates is now handled by that function.

Note that besides moving stuff around, it also fixes a bug: previously, we did not know to resolve the referenced repository's common directory. Consequently, when referencing a worktree we failed to resolve alternates. But as `collect_alternates()` already knows to resolve the commondir for "--local" we can simply reuse this resolved path for our purpose.

Add two tests, the first one of which exercises this bug to avoid future regressions. The second test ensures that we properly handle relative alternates for a referenced worktree.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c            | 34 +++++++++++++++++++++++-----------
 t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++
 2 files changed, 48 insertions(+), 11 deletions(-)
Show changes to 2 files +48 −11

builtin/clone.c, t/t5604-clone-reference.sh

diff --git a/builtin/clone.c b/builtin/clone.c
index d397fd36b2..17353a8e1f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -181,7 +181,7 @@ static int collect_one_alternate(struct string_list_item *item, void *cb_data)
 	return 0;
 }
 
-static void copy_alternates(struct strbuf *src, const char *src_repo)
+static void read_alternates(struct strvec *alternates, const char *src_repo)
 {
 	/*
 	 * Read from the source objects/info/alternates file
@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
 	 * to turn entries with paths relative to the original
 	 * absolute, so that they can be used in the new repository.
 	 */
-	FILE *in = xfopen(src->buf, "r");
+	FILE *in;
+	struct strbuf path = STRBUF_INIT;
 	struct strbuf line = STRBUF_INIT;
 
+	strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
+
+	in = fopen(path.buf, "r");
+	if (!in) {
+		if (errno == ENOENT)
+			goto out;
+		die_errno("could not read alternates file '%s'", path.buf);
+	}
+
 	while (strbuf_getline(&line, in) != EOF) {
 		char *abs_path;
 		if (!line.len || line.buf[0] == '#')
 			continue;
 		if (is_absolute_path(line.buf)) {
-			odb_add_to_alternates_file(the_repository->objects,
-						   line.buf);
+			strvec_push(alternates, line.buf);
 			continue;
 		}
 		abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
 		if (!normalize_path_copy(abs_path, abs_path))
-			odb_add_to_alternates_file(the_repository->objects,
-						   abs_path);
+			strvec_push(alternates, abs_path);
 		else
 			warning("skipping invalid relative alternate: %s/%s",
 				src_repo, line.buf);
 		free(abs_path);
 	}
+
+out:
+	strbuf_release(&path);
 	strbuf_release(&line);
-	fclose(in);
+	if (in)
+		fclose(in);
 }
 
 static void collect_alternates(struct strvec *alternates,
@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,
 		get_common_dir(&commondir, local_source_repo);
 		if (option_shared)
 			strvec_pushf(alternates, "%s/objects", commondir.buf);
+		else
+			read_alternates(alternates, commondir.buf);
 
 		strbuf_release(&commondir);
 	}
@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 			continue;
 		}
 
-		/* Files that cannot be copied bit-for-bit... */
-		if (!fspathcmp(iter->relative_path, "info/alternates")) {
-			copy_alternates(src, src_repo);
+		/* Alternates were already handled earlier. */
+		if (!fspathcmp(iter->relative_path, "info/alternates"))
 			continue;
-		}
 
 		if (unlink(dest->buf) && errno != ENOENT)
 			die_errno(_("failed to unlink '%s'"), dest->buf);
diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
index 39a0c318df..9e4b98fdb8 100755
--- a/t/t5604-clone-reference.sh
+++ b/t/t5604-clone-reference.sh
@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '
 	git clone --no-local --reference graph.git --dissociate orig clone
 '
 
+test_expect_success 'local clone from linked worktree carries over alternates' '
+	rm -fr base derived derived-wt dst expect &&
+	git init base &&
+	test_commit -C base one &&
+	git clone --shared base derived &&
+	git -C derived worktree add ../derived-wt &&
+	git clone derived-wt dst &&
+	echo "$(pwd)/base/.git/objects" >expect &&
+	test_cmp expect dst/.git/objects/info/alternates &&
+	git -C dst fsck
+'
+
+test_expect_success 'local clone from linked worktree resolves relative alternates' '
+	rm -fr base derived derived-wt dst expect &&
+	git init base &&
+	test_commit -C base one &&
+	git clone --shared base derived &&
+	echo "../../../base/.git/objects" >derived/.git/objects/info/alternates &&
+	git -C derived worktree add ../derived-wt &&
+	git clone derived-wt dst &&
+	echo "$(pwd)/base/.git/objects" >expect &&
+	test_cmp expect dst/.git/objects/info/alternates &&
+	git -C dst fsck
+'
+
 test_done
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 10, 2026, 15:09 UTC in reply to Patrick Steinhardt on lore

[PATCH v5 7/9] odb/source: support writing alternates when creating the database

Add the ability to write alternates when creating the object database. This change allows us to remove the `write_alternates()` callback in a subsequent patch.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 odb/source.h       | 17 +++++++++---
 setup.c            |  4 ++-
 3 files changed, 91 insertions(+), 6 deletions(-)
Show changes to 3 files +91 −6

odb/source-files.c, odb/source.h, setup.c

diff --git a/odb/source-files.c b/odb/source-files.c
index b7b3a297bb..8fe65d91f8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -18,6 +18,7 @@
 #include "run-command.h"
 #include "strbuf.h"
 #include "string-list.h"
+#include "strmap.h"
 #include "strvec.h"
 #include "tree.h"
 #include "write-or-die.h"
@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)
 	odb_source_close(&files->packed->base);
 }
 
-static int odb_source_files_create_on_disk(struct odb_source *source)
+static int odb_source_files_create_on_disk(struct odb_source *source,
+					   const struct odb_create_on_disk_options *opts)
 {
+	struct lock_file alternates_lock = LOCK_INIT;
 	struct strbuf path = STRBUF_INIT;
+	struct strset seen = STRSET_INIT;
+	struct strbuf line = STRBUF_INIT;
+	int ret;
 
 	safe_create_dir(source->odb->repo, source->path, 1);
 
@@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
 	strbuf_addf(&path, "%s/info", source->path);
 	safe_create_dir(source->odb->repo, path.buf, 1);
 
+	if (opts->alternates && opts->alternates->nr) {
+		FILE *alternates, *orig;
+
+		strbuf_reset(&path);
+		strbuf_addf(&path, "%s/info/alternates", source->path);
+
+		repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,
+					       path.buf, LOCK_DIE_ON_ERROR);
+
+		alternates = fdopen_lock_file(&alternates_lock, "w");
+		if (!alternates) {
+			ret = error_errno(_("unable to fdopen alternates lockfile"));
+			goto out;
+		}
+
+		/*
+		 * The alternates file may already exist, e.g. when it has been
+		 * seeded from a template directory. Read any preexisting
+		 * entries so that we don't end up writing duplicates.
+		 */
+		orig = fopen(path.buf, "r");
+		if (orig) {
+			while (strbuf_getline(&line, orig) != EOF) {
+				strset_add(&seen, line.buf);
+				fprintf(alternates, "%s\n", line.buf);
+			}
+
+			if (ferror(orig)) {
+				ret = error_errno(_("unable to read alternates file"));
+				fclose(orig);
+				goto out;
+			}
+
+			fclose(orig);
+		} else if (errno != ENOENT) {
+			ret = error_errno(_("unable to read alternates file"));
+			goto out;
+		}
+
+		for (size_t i = 0; i < opts->alternates->nr; i++) {
+			const char *alternate = opts->alternates->v[i];
+			if (!strset_add(&seen, alternate))
+				continue;
+			fprintf(alternates, "%s\n", alternate);
+		}
+
+		if (ferror(alternates)) {
+			ret = error_errno(_("unable to write alternates file"));
+			goto out;
+		}
+
+		if (commit_lock_file(&alternates_lock)) {
+			ret = error_errno(_("unable to commit alternates file"));
+			goto out;
+		}
+	}
+
+	/* Reprepare the object database to activate alternates. */
+	odb_reprepare(source->odb);
+
+	ret = 0;
+
+out:
+	rollback_lock_file(&alternates_lock);
+	strbuf_release(&line);
 	strbuf_release(&path);
-	return 0;
+	strset_clear(&seen);
+	return ret;
 }
 
 static void odb_source_files_prepare(struct odb_source *source,
diff --git a/odb/source.h b/odb/source.h
index ea8675247e..63f1c0c531 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -36,6 +36,15 @@ struct object_id;
 struct odb_stream;
 struct strvec;
 
+struct odb_create_on_disk_options {
+	/*
+	 * Alternates that shall be written into the newly created object
+	 * database. Whether or not this option can be handled is specific to
+	 * the backend.
+	 */
+	const struct strvec *alternates;
+};
+
 /*
  * The source is the part of the object database that stores the actual
  * objects. It thus encapsulates the logic to read and write the specific
@@ -106,7 +115,8 @@ struct odb_source {
 	 * This callback may be NULL in case the source does not need any
 	 * on-disk setup.
 	 */
-	int (*create_on_disk)(struct odb_source *source);
+	int (*create_on_disk)(struct odb_source *source,
+			      const struct odb_create_on_disk_options *opts);
 
 	/*
 	 * This callback is expected to prepare the source so that it becomes
@@ -356,11 +366,12 @@ static inline void odb_source_close(struct odb_source *source)
  * Create on-disk data structures that are required for this source to operate
  * correctly. Returns 0 on success, a negative error code otherwise.
  */
-static inline int odb_source_create_on_disk(struct odb_source *source)
+static inline int odb_source_create_on_disk(struct odb_source *source,
+					    const struct odb_create_on_disk_options *opts)
 {
 	if (!source->create_on_disk)
 		return 0;
-	return source->create_on_disk(source);
+	return source->create_on_disk(source, opts);
 }
 
 /*
diff --git a/setup.c b/setup.c
index 8c7b97f92e..37a8e6f124 100644
--- a/setup.c
+++ b/setup.c
@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,
 
 void create_object_database(struct repository *repo)
 {
+	struct odb_create_on_disk_options opts = { 0 };
+
 	/*
 	 * Create the "objects" directory in the common directory. This is done
 	 * so that the repository can be discovered regardless of the backend
@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)
 
 	repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);
 
-	if (odb_source_create_on_disk(repo->objects->sources) < 0)
+	if (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)
 		die(_("failed creating object database"));
 }
 
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 10, 2026, 15:09 UTC in reply to Patrick Steinhardt on lore

[PATCH v5 8/9] builtin/clone: write alternates via `odb_create_on_disk()`

When creating a repository with alternates we first initialize the object database and then write alternates to it in a separate step. This is unfortunate due to a couple of reasons:

  - It requires us to have a `write_alternates()` callback, which is
    unfortunate as we never even write alternates to an object database
    after it has been created.
  - We're about to make alternates an implementation detail of the
    object database's backend in a future patch series, so having this
    callback is suboptimal there.
  - The backend has more flexibility with how exactly alternates are
    configured when it itself is in full control over their setup at the
    time where it creates the object database itself.

We have thus introduced the ability to write alternates right at creation time in the preceding commits, and we have unified setup of alternates into a single location. All that's left to do for us now is to wire up alternates as an option for the database creation.

Do so.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/clone.c   | 5 +----
 builtin/init-db.c | 2 +-
 setup.c           | 7 +++++--
 setup.h           | 6 ++++--
 4 files changed, 11 insertions(+), 9 deletions(-)
Show changes to 4 files +11 −9

builtin/clone.c, builtin/init-db.c, setup.c, setup.h

diff --git a/builtin/clone.c b/builtin/clone.c
index 17353a8e1f..b14264c33a 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1364,11 +1364,8 @@ int cmd_clone(int argc,
 	if (option_local > 0 && !is_local)
 		warning(_("--local is ignored"));
 
-	create_object_database(the_repository);
 	collect_alternates(&alternates, is_local ? path : NULL);
-
-	for (size_t i = 0; i < alternates.nr; i++)
-		odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
+	create_object_database(the_repository, &alternates);
 
 	transport = transport_get(remote, path ? path : remote->url.v[0]);
 	transport_set_verbosity(transport, option_verbosity, option_progress);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index f2c7e3be6d..5c22eae2f3 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -251,7 +251,7 @@ int cmd_init_db(int argc,
 			  template_dir, hash_algo, ref_storage_format,
 			  init_shared_repository, &reinit);
 	create_reference_database(the_repository, initial_branch, quiet);
-	create_object_database(the_repository);
+	create_object_database(the_repository, NULL);
 
 	if (!quiet) {
 		int len = strlen(git_dir);
diff --git a/setup.c b/setup.c
index 37a8e6f124..17d0d25973 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,
 	return reinit;
 }
 
-void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo,
+			    const struct strvec *alternates)
 {
-	struct odb_create_on_disk_options opts = { 0 };
+	struct odb_create_on_disk_options opts = {
+		.alternates = alternates,
+	};
 
 	/*
 	 * Create the "objects" directory in the common directory. This is done
diff --git a/setup.h b/setup.h
index f1c1ed65fb..27b2492373 100644
--- a/setup.h
+++ b/setup.h
@@ -291,9 +291,11 @@ void create_reference_database(struct repository *repo, const char *initial_bran
 
 /*
  * Create the object database for the repository. The repository must have
- * already been configured properly before calling this function.
+ * already been configured properly before calling this function. When set,
+ * `alternates` is the list of alternates that should be written into the
+ * object database.
  */
-void create_object_database(struct repository *repo);
+void create_object_database(struct repository *repo, const struct strvec *alternates);
 
 /*
  * NOTE NOTE NOTE!!
-- 
2.55.0.1074.ge7621b4bad.dirty
Patrick SteinhardtSep 10, 2026, 15:09 UTC in reply to Patrick Steinhardt on lore

[PATCH v5 9/9] odb/source: remove the ability to write alternates

There are no users of `odb_source_write_alternates()` in our tree anymore. Remove that function and its supporting infrastructure.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb.c                 |  9 ---------
 odb.h                 |  7 -------
 odb/source-files.c    | 54 ---------------------------------------------------
 odb/source-inmemory.c |  7 -------
 odb/source-loose.c    |  7 -------
 odb/source-packed.c   |  7 -------
 odb/source.h          | 26 -------------------------
 7 files changed, 117 deletions(-)
Show changes to 7 files +0 −117

odb.c, odb.h, odb/source-files.c, odb/source-inmemory.c, odb/source-loose.c, odb/source-packed.c, odb/source.h

diff --git a/odb.c b/odb.c
index 67d98d64fc..b531cf8fb3 100644
--- a/odb.c
+++ b/odb.c
@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
 	return alternate;
 }
 
-void odb_add_to_alternates_file(struct object_database *odb,
-				const char *dir)
-{
-	int ret = odb_source_write_alternate(odb->sources, dir);
-	if (ret < 0)
-		die(NULL);
-	odb_add_alternate_recursively(odb, dir, 0);
-}
-
 struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
 						const char *dir)
 {
diff --git a/odb.h b/odb.h
index b9e0db56ec..2d002461f8 100644
--- a/odb.h
+++ b/odb.h
@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,
  */
 int odb_has_alternates(struct object_database *odb);
 
-/*
- * Add the directory to the on-disk alternates file; the new entry will also
- * take effect in the current process.
- */
-void odb_add_to_alternates_file(struct object_database *odb,
-				const char *dir);
-
 /*
  * Add the directory to the in-memory list of alternate sources (along with any
  * recursive alternates it points to), but do not modify the on-disk alternates
diff --git a/odb/source-files.c b/odb/source-files.c
index 8fe65d91f8..b3f340dff8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -306,59 +306,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,
 	return 0;
 }
 
-static int odb_source_files_write_alternate(struct odb_source *source,
-					    const char *alternate)
-{
-	struct lock_file lock = LOCK_INIT;
-	char *path = xstrfmt("%s/%s", source->path, "info/alternates");
-	FILE *in, *out;
-	int found = 0;
-	int ret;
-
-	repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
-				       LOCK_DIE_ON_ERROR);
-	out = fdopen_lock_file(&lock, "w");
-	if (!out) {
-		ret = error_errno(_("unable to fdopen alternates lockfile"));
-		goto out;
-	}
-
-	in = fopen(path, "r");
-	if (in) {
-		struct strbuf line = STRBUF_INIT;
-
-		while (strbuf_getline(&line, in) != EOF) {
-			if (!strcmp(alternate, line.buf)) {
-				found = 1;
-				break;
-			}
-			fprintf_or_die(out, "%s\n", line.buf);
-		}
-
-		strbuf_release(&line);
-		fclose(in);
-	} else if (errno != ENOENT) {
-		ret = error_errno(_("unable to read alternates file"));
-		goto out;
-	}
-
-	if (found) {
-		rollback_lock_file(&lock);
-	} else {
-		fprintf_or_die(out, "%s\n", alternate);
-		if (commit_lock_file(&lock)) {
-			ret = error_errno(_("unable to move new alternates file into place"));
-			goto out;
-		}
-	}
-
-	ret = 0;
-
-out:
-	free(path);
-	return ret;
-}
-
 static int too_many_loose_objects(struct odb_source_files *files, int limit)
 {
 	unsigned long loose_count;
@@ -842,7 +789,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
 	files->base.write_object_stream = odb_source_files_write_object_stream;
 	files->base.begin_transaction = odb_source_files_begin_transaction;
 	files->base.read_alternates = odb_source_files_read_alternates;
-	files->base.write_alternate = odb_source_files_write_alternate;
 	files->base.optimize = odb_source_files_optimize;
 	files->base.optimize_required = odb_source_files_optimize_required;
 
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
index 795672adf2..b00248dfb2 100644
--- a/odb/source-inmemory.c
+++ b/odb/source-inmemory.c
@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,
-					       const char *alternate UNUSED)
-{
-	return error("in-memory source does not support alternates");
-}
-
 static void odb_source_inmemory_close(struct odb_source *source UNUSED)
 {
 }
@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
 	source->base.freshen_object = odb_source_inmemory_freshen_object;
 	source->base.begin_transaction = odb_source_inmemory_begin_transaction;
 	source->base.read_alternates = odb_source_inmemory_read_alternates;
-	source->base.write_alternate = odb_source_inmemory_write_alternate;
 
 	return source;
 }
diff --git a/odb/source-loose.c b/odb/source-loose.c
index bb3455dfbd..0f9b30bac1 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,
-					    const char *alternate UNUSED)
-{
-	return error("loose source does not support alternates");
-}
-
 static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
 {
 	oidtree_clear(loose->cache);
@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
 	loose->base.write_object_stream = odb_source_loose_write_object_stream;
 	loose->base.begin_transaction = odb_source_loose_begin_transaction;
 	loose->base.read_alternates = odb_source_loose_read_alternates;
-	loose->base.write_alternate = odb_source_loose_write_alternate;
 
 	if (!is_absolute_path(loose->base.path))
 		chdir_notify_register(NULL, odb_source_loose_reparent, loose);
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 630d955585..c2d253759c 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,
 	return 0;
 }
 
-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,
-					     const char *alternate UNUSED)
-{
-	return error("packed backend cannot write alternates");
-}
-
 void (*report_garbage)(unsigned seen_bits, const char *path);
 
 static void report_helper(const struct string_list *list,
@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
 	packed->base.write_object_stream = odb_source_packed_write_object_stream;
 	packed->base.begin_transaction = odb_source_packed_begin_transaction;
 	packed->base.read_alternates = odb_source_packed_read_alternates;
-	packed->base.write_alternate = odb_source_packed_write_alternate;
 
 	if (!is_absolute_path(path))
 		chdir_notify_register(NULL, odb_source_packed_reparent, packed);
diff --git a/odb/source.h b/odb/source.h
index 63f1c0c531..693a9fc604 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -286,19 +286,6 @@ struct odb_source {
 	int (*read_alternates)(struct odb_source *source,
 			       struct strvec *out);
 
-	/*
-	 * This callback is expected to persist the singular alternate passed
-	 * to it into its list of alternates. Any pre-existing alternates are
-	 * expected to remain active. Subsequent calls to `read_alternates` are
-	 * thus expected to yield the pre-existing list of alternates plus the
-	 * newly added alternate appended to its end.
-	 *
-	 * The callback is expected to return 0 on success, a negative error
-	 * code otherwise.
-	 */
-	int (*write_alternate)(struct odb_source *source,
-			       const char *alternate);
-
 	/*
 	 * This callback is expected to optimize the object database source.
 	 * Returns 0 on success, a negative error code otherwise.
@@ -518,19 +505,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,
 	return source->read_alternates(source, out);
 }
 
-/*
- * Write and persist a new alternate object database source for the given
- * source. Any preexisting alternates are expected to stay valid, and the new
- * alternate shall be appended to the end of the list.
- *
- * Returns 0 on success, a negative error code otherwise.
- */
-static inline int odb_source_write_alternate(struct odb_source *source,
-					      const char *alternate)
-{
-	return source->write_alternate(source, alternate);
-}
-
 /*
  * Create a new transaction that can be used to write objects into a temporary
  * staging area. The objects will only be persisted when the transaction is
-- 
2.55.0.1074.ge7621b4bad.dirty
Karthik NayakSep 10, 2026, 19:53 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v5 0/9] odb: write alternates at creation time

Patrick Steinhardt <ps@pks.im> writes:
Show 42 quoted lines
> Hi,
>
> writing alternates into the object database currently happens via
> `odb_source_write_alternate()`. But while that creates the ability to
> create alternates at arbitrary points of a source's lifetime, we don't
> use that functionality in the first place. Instead, we only ever write
> alternates when creating a new repository.
>
> This design is suboptimal due to a couple of reasons:
>
>   - It requires us to have a `write_alternates()` callback, which is
>     overblown as we never even write alternates to an object database
>     after it has been created.
>
>   - We're about to make alternates an implementation detail of the
>     object database's backend in a future patch series, so alternate
>     implementations may not even support them.
>
>   - The backend has more flexibility with how exactly alternates are
>     configured when it itself is in full control over their setup at the
>     time where it creates the object database itself.
>
> This patch series thus refactors how we handle alternates so that we
> don't write them ad-hoc anymore. Instead, the series introduces a new
> option for `odb_source_create_on_disk()` that makes it handle those
> alternates at creation time.
>
> This is part of the bigger goal of moving handling of alternates into
> the "files" backend.
>
> This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)
> with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop
> `alternates_db` field, 2026-08-17) merged into it.
>
> Changes in v5:
>   - Rename `add_one_alternate` and `add_one_alternate_data` to
>     `collect_one_alternate` and `collect_alternates_data` to clarify
>     their intent a bit.
>   - Drop extra parameter in `collect_alternates()`.
>   - Clarify why we compute `commondir` even though it's unused.
>   - Link to v4: https://patch.msgid.link/20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im
>

Looks like you missed my comment/question in CAOLa=ZQaPstiQmXm9=TyWPUxL6X2=Lcqeg6y2XeXzSJDpq-GBA@mail.gmail.com, but otherwise looks good :)

[snip]
Patrick SteinhardtSep 11, 2026, 05:15 UTC in reply to Karthik Nayak on lore

Re: [PATCH v4 7/9] odb/source: support writing alternates when creating the database

On Thu, Sep 10, 2026 at 04:10:31AM -0700, Karthik Nayak wrote:
Show 6 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/odb/source-files.c b/odb/source-files.c
> > index b7b3a297bb..8fe65d91f8 100644
> > --- a/odb/source-files.c
> > +++ b/odb/source-files.c
> > @@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
[snip]
Show 19 quoted lines
> > +		/*
> > +		 * The alternates file may already exist, e.g. when it has been
> > +		 * seeded from a template directory. Read any preexisting
> > +		 * entries so that we don't end up writing duplicates.
> > +		 */
> > +		orig = fopen(path.buf, "r");
> > +		if (orig) {
> > +			while (strbuf_getline(&line, orig) != EOF) {
> > +				strset_add(&seen, line.buf);
> > +				fprintf(alternates, "%s\n", line.buf);
> > +			}
> > +
> > +			if (ferror(orig)) {
> > +				ret = error_errno(_("unable to read alternates file"));
> > +				fclose(orig);
> > +				goto out;
> > +			}
> 
> Shouldn't this be checked inside the for loop with every `fprintf` call?

There isn't really any need to, as the error indicator on file streams is sticky. Sure, it would allow us to potentially abort earlier. But it's unlikely that this really matters in practice.

Show 17 quoted lines
> > diff --git a/odb/source.h b/odb/source.h
> > index ea8675247e..63f1c0c531 100644
> > --- a/odb/source.h
> > +++ b/odb/source.h
> > @@ -36,6 +36,15 @@ struct object_id;
> >  struct odb_stream;
> >  struct strvec;
> >
> > +struct odb_create_on_disk_options {
> > +	/*
> > +	 * Alternates that shall be written into the newly created object
> > +	 * database. Whether or not this option can be handled is specific to
> > +	 * the backend.
> > +	 */
> 
> Would it make sense to formalize errors thrown by backends, so we know
> when a backend specifically cannot handle alternates?

Maybe, but there's nothing that'd use it. So I'm a bit hesitant to introduce that right now without us having a way to verify the logic at all. We may want to eventually introduce such logic though.

Patrick
Patrick SteinhardtSep 11, 2026, 05:15 UTC in reply to Karthik Nayak on lore

Re: [PATCH v5 0/9] odb: write alternates at creation time

On Thu, Sep 10, 2026 at 12:53:21PM -0700, Karthik Nayak wrote:
Show 48 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > Hi,
> >
> > writing alternates into the object database currently happens via
> > `odb_source_write_alternate()`. But while that creates the ability to
> > create alternates at arbitrary points of a source's lifetime, we don't
> > use that functionality in the first place. Instead, we only ever write
> > alternates when creating a new repository.
> >
> > This design is suboptimal due to a couple of reasons:
> >
> >   - It requires us to have a `write_alternates()` callback, which is
> >     overblown as we never even write alternates to an object database
> >     after it has been created.
> >
> >   - We're about to make alternates an implementation detail of the
> >     object database's backend in a future patch series, so alternate
> >     implementations may not even support them.
> >
> >   - The backend has more flexibility with how exactly alternates are
> >     configured when it itself is in full control over their setup at the
> >     time where it creates the object database itself.
> >
> > This patch series thus refactors how we handle alternates so that we
> > don't write them ad-hoc anymore. Instead, the series introduces a new
> > option for `odb_source_create_on_disk()` that makes it handle those
> > alternates at creation time.
> >
> > This is part of the bigger goal of moving handling of alternates into
> > the "files" backend.
> >
> > This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)
> > with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop
> > `alternates_db` field, 2026-08-17) merged into it.
> >
> > Changes in v5:
> >   - Rename `add_one_alternate` and `add_one_alternate_data` to
> >     `collect_one_alternate` and `collect_alternates_data` to clarify
> >     their intent a bit.
> >   - Drop extra parameter in `collect_alternates()`.
> >   - Clarify why we compute `commondir` even though it's unused.
> >   - Link to v4: https://patch.msgid.link/20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im
> >
> 
> Looks like you missed my comment/question in
> CAOLa=ZQaPstiQmXm9=TyWPUxL6X2=Lcqeg6y2XeXzSJDpq-GBA@mail.gmail.com, but
> otherwise looks good :)

Oh, indeed, I somehow overlooked that mail. Replied to it now, but I don't think it requires further changes. Thanks!

Patrick
Karthik NayakSep 14, 2026, 10:22 UTC in reply to Patrick Steinhardt on lore

Re: [PATCH v5 0/9] odb: write alternates at creation time

Patrick Steinhardt <ps@pks.im> writes:
Show 54 quoted lines
> On Thu, Sep 10, 2026 at 12:53:21PM -0700, Karthik Nayak wrote:
>> Patrick Steinhardt <ps@pks.im> writes:
>>
>> > Hi,
>> >
>> > writing alternates into the object database currently happens via
>> > `odb_source_write_alternate()`. But while that creates the ability to
>> > create alternates at arbitrary points of a source's lifetime, we don't
>> > use that functionality in the first place. Instead, we only ever write
>> > alternates when creating a new repository.
>> >
>> > This design is suboptimal due to a couple of reasons:
>> >
>> >   - It requires us to have a `write_alternates()` callback, which is
>> >     overblown as we never even write alternates to an object database
>> >     after it has been created.
>> >
>> >   - We're about to make alternates an implementation detail of the
>> >     object database's backend in a future patch series, so alternate
>> >     implementations may not even support them.
>> >
>> >   - The backend has more flexibility with how exactly alternates are
>> >     configured when it itself is in full control over their setup at the
>> >     time where it creates the object database itself.
>> >
>> > This patch series thus refactors how we handle alternates so that we
>> > don't write them ad-hoc anymore. Instead, the series introduces a new
>> > option for `odb_source_create_on_disk()` that makes it handle those
>> > alternates at creation time.
>> >
>> > This is part of the bigger goal of moving handling of alternates into
>> > the "files" backend.
>> >
>> > This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)
>> > with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop
>> > `alternates_db` field, 2026-08-17) merged into it.
>> >
>> > Changes in v5:
>> >   - Rename `add_one_alternate` and `add_one_alternate_data` to
>> >     `collect_one_alternate` and `collect_alternates_data` to clarify
>> >     their intent a bit.
>> >   - Drop extra parameter in `collect_alternates()`.
>> >   - Clarify why we compute `commondir` even though it's unused.
>> >   - Link to v4: https://patch.msgid.link/20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im
>> >
>>
>> Looks like you missed my comment/question in
>> CAOLa=ZQaPstiQmXm9=TyWPUxL6X2=Lcqeg6y2XeXzSJDpq-GBA@mail.gmail.com, but
>> otherwise looks good :)
>
> Oh, indeed, I somehow overlooked that mail. Replied to it now, but I
> don't think it requires further changes. Thanks!
>
> Patrick
With your response, I have nothing more to add! Thanks

Back to recent threads