Git development
 help / color / mirror / Atom feed
* [PATCH 0/7] odb: introduce object storage extension
@ 2026-10-19  8:55 Patrick Steinhardt
  2026-10-19  8:55 ` [PATCH 1/7] loose: drop unused `repo_write_loose_object_map()` Patrick Steinhardt
                   ` (8 more replies)
  0 siblings, 9 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-19  8:55 UTC (permalink / raw)
  To: git

Hi,

this patch series introduces the object storage extension. The extension
is of course similar in spirit and design to the ref storage extension:
it can be configured via "extensions.objectStorage", has the same syntax
with "<schema>[://<payload>]", and we have the same mechanisms to
configure it ("--object-storage-format=", GIT_OBJECT_STORAGE_FORMAT,
GIT_DEFAULT_OBJECT_STORAGE_FORMAT, "init.defaultObjetcStorageFormat").

For now, we only support the "files" object storage format, so not a lot
of stuff really changes. That being said, its optional payload allows
you to configure a different location for the ".git/objects" directory.
So if you specify "extensions.objectStorage=files://objs", objects would
be stored in ".git/objs" instead.

The series is built on top of c51408889c (The 6th batch, 2026-10-16)
with the following dependencies merged into it:

  - ps/odb-files-alternates at 0ba59c8453 (odb/source: drop
    `read_alternates` callback, 2026-10-08) merged into it.

  - ps/setup-enforce-repo-passed-to-create-repository-has-no-state
    (phew, what a mouthful) at 0ba59c8453 (odb/source: drop
    `read_alternates` callback, 2026-10-08).

Note that these two series conflict with one another. The conflict can
easily be resolved by just deleting the whole conflicting sections.

Thanks!

Patrick

---
Patrick Steinhardt (7):
      loose: drop unused `repo_write_loose_object_map()`
      setup: introduce object storage extension
      setup: allow overriding object storage format via environment
      builtin: expose the object storage format
      builtin/init: introduce handling of object storage format
      builtin/clone: introduce "--object-storage-format="
      builtin/submodule: propagate "--object-storage-format="

 Documentation/config/extensions.adoc       |  23 +++
 Documentation/config/init.adoc             |   5 +
 Documentation/git-clone.adoc               |  11 ++
 Documentation/git-init.adoc                |  10 +-
 Documentation/git-repo.adoc                |   6 +
 Documentation/git-rev-parse.adoc           |   5 +
 Documentation/git-submodule.adoc           |  10 +-
 Documentation/git.adoc                     |  10 ++
 Documentation/object-storage-format.adoc   |   6 +
 builtin/clone.c                            |  58 +++++++-
 builtin/init-db.c                          |   7 +-
 builtin/repo.c                             |  10 ++
 builtin/rev-parse.c                        |   7 +
 builtin/submodule--helper.c                |  28 ++++
 environment.h                              |   1 +
 git-submodule.sh                           |  18 +++
 loose.c                                    |  46 ------
 loose.h                                    |   1 -
 odb.c                                      |   2 +-
 odb/source-files.c                         |   7 +
 odb/source-files.h                         |   1 +
 odb/source.c                               |  48 ++++++-
 odb/source.h                               |  12 +-
 repository.c                               |   8 ++
 repository.h                               |   8 ++
 setup.c                                    | 116 ++++++++++++++-
 setup.h                                    |   2 +
 t/meson.build                              |   1 +
 t/t0001-init.sh                            | 221 +++++++++++++++++++++++++++++
 t/t1900-repo-info.sh                       |   3 +
 t/t5601-clone.sh                           |  68 +++++++++
 t/t7427-submodule-object-storage-format.sh |  91 ++++++++++++
 32 files changed, 788 insertions(+), 62 deletions(-)


---
base-commit: 0d31382da9336683214372ba328fe9ecbc8bcb9a
change-id: 20261006-b4-pks-odb-extension-36de693bb943


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH 1/7] loose: drop unused `repo_write_loose_object_map()`
  2026-10-19  8:55 [PATCH 0/7] odb: introduce object storage extension Patrick Steinhardt
@ 2026-10-19  8:55 ` Patrick Steinhardt
  2026-10-19 20:21   ` Karthik Nayak
  2026-10-19  8:55 ` [PATCH 2/7] setup: introduce object storage extension Patrick Steinhardt
                   ` (7 subsequent siblings)
  8 siblings, 1 reply; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-19  8:55 UTC (permalink / raw)
  To: git

The function `repo_write_loose_object_map()` was introduced in
23b2c7e95b (loose: add a mapping between SHA-1 and SHA-256 for loose
objects, 2023-10-01), but it never had any callers as loose object maps
are instead written incrementally via `repo_add_loose_object_map()`
whenever a new loose object is created.

Besides being dead code, the function also hardcodes the location of the
"loose-object-idx" file as "$GIT_COMMON_DIR/objects", whereas both the
reader and `repo_add_loose_object_map()` derive the location from the
loose source's path. The function would thus disagree with everyone else
where the loose object map is supposed to be located when the object
directory is using a non-default location.

Remove the function.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 loose.c | 46 ----------------------------------------------
 loose.h |  1 -
 2 files changed, 47 deletions(-)

diff --git a/loose.c b/loose.c
index d6a2bb5ba3..7e26a04ec0 100644
--- a/loose.c
+++ b/loose.c
@@ -123,52 +123,6 @@ int repo_read_loose_object_map(struct repository *repo)
 	return 0;
 }
 
-int repo_write_loose_object_map(struct repository *repo)
-{
-	struct odb_source_files *files = odb_source_files_downcast(repo->objects->source);
-	kh_oid_map_t *map = files->dirs->loose->map->to_compat;
-	struct lock_file lock;
-	int fd;
-	khiter_t iter;
-	struct strbuf buf = STRBUF_INIT, path = STRBUF_INIT;
-
-	if (!should_use_loose_object_map(repo))
-		return 0;
-
-	repo_common_path_replace(repo, &path, "objects/loose-object-idx");
-	fd = repo_hold_lock_file_for_update_timeout(repo, &lock, path.buf,
-						    LOCK_DIE_ON_ERROR, -1);
-	iter = kh_begin(map);
-	if (write_in_full(fd, loose_object_header, strlen(loose_object_header)) < 0)
-		goto errout;
-
-	for (; iter != kh_end(map); iter++) {
-		if (kh_exist(map, iter)) {
-			if (oideq(&kh_key(map, iter), repo->hash_algo->empty_tree) ||
-			    oideq(&kh_key(map, iter), repo->hash_algo->empty_blob))
-				continue;
-			strbuf_addf(&buf, "%s %s\n", oid_to_hex(&kh_key(map, iter)), oid_to_hex(kh_value(map, iter)));
-			if (write_in_full(fd, buf.buf, buf.len) < 0)
-				goto errout;
-			strbuf_reset(&buf);
-		}
-	}
-	strbuf_release(&buf);
-	if (commit_lock_file(&lock) < 0) {
-		error_errno(_("could not write loose object index %s"), path.buf);
-		strbuf_release(&path);
-		return -1;
-	}
-	strbuf_release(&path);
-	return 0;
-errout:
-	rollback_lock_file(&lock);
-	strbuf_release(&buf);
-	error_errno(_("failed to write loose object index %s"), path.buf);
-	strbuf_release(&path);
-	return -1;
-}
-
 static int write_one_object(struct odb_source_loose *loose,
 			    const struct object_id *oid,
 			    const struct object_id *compat_oid)
diff --git a/loose.h b/loose.h
index ed663ac550..41c4b46818 100644
--- a/loose.h
+++ b/loose.h
@@ -22,6 +22,5 @@ int repo_add_loose_object_map(struct odb_source_loose *loose,
 			      const struct object_id *oid,
 			      const struct object_id *compat_oid);
 int repo_read_loose_object_map(struct repository *repo);
-int repo_write_loose_object_map(struct repository *repo);
 
 #endif

-- 
2.56.0.269.g68944adf51.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH 2/7] setup: introduce object storage extension
  2026-10-19  8:55 [PATCH 0/7] odb: introduce object storage extension Patrick Steinhardt
  2026-10-19  8:55 ` [PATCH 1/7] loose: drop unused `repo_write_loose_object_map()` Patrick Steinhardt
@ 2026-10-19  8:55 ` Patrick Steinhardt
  2026-10-19 21:29   ` Karthik Nayak
  2026-10-19  8:55 ` [PATCH 3/7] setup: allow overriding object storage format via environment Patrick Steinhardt
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-19  8:55 UTC (permalink / raw)
  To: git

Introduce a new "extensions.objectStorage" extension that allows the
user to configure the primary object database source of a repository.
The value is a URI in the format "<schema>[://<payload>]", mirroring
the format used by "extensions.refStorage":

  - The schema identifies the backend that shall be used. For now the
    only supported schema is "files", which is the existing object
    database backed by loose objects and packfiles.

  - The payload can be passed to configure the backend, and its
    interpretation is backend-specific. For the "files" backend, it
    allows you to configure a different location for the ".git/objects"
    directory.

Eventually, this mechanism will be used to support alternative backends.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/config/extensions.adoc     | 23 +++++++++++++++
 Documentation/object-storage-format.adoc |  6 ++++
 odb.c                                    |  2 +-
 odb/source-files.c                       |  7 +++++
 odb/source-files.h                       |  1 +
 odb/source.c                             | 48 +++++++++++++++++++++++++++++++-
 odb/source.h                             | 12 +++++++-
 repository.c                             |  8 ++++++
 repository.h                             |  8 ++++++
 setup.c                                  | 13 +++++++++
 setup.h                                  |  1 +
 t/t0001-init.sh                          | 30 ++++++++++++++++++++
 12 files changed, 156 insertions(+), 3 deletions(-)

diff --git a/Documentation/config/extensions.adoc b/Documentation/config/extensions.adoc
index be6678bb5b..4f3a66a454 100644
--- a/Documentation/config/extensions.adoc
+++ b/Documentation/config/extensions.adoc
@@ -56,6 +56,29 @@ preciousObjects:::
 For historical reasons, this extension is respected regardless of the
 `core.repositoryFormatVersion` setting.
 
+objectStorage:::
+	Specify the object storage format and a corresponding payload. The
+	value can be either a format name or a URI:
++
+--
+* A format name alone (e.g., `files`).
+
+* A URI format `<format>://<payload>` explicitly specifies both the
+  format and payload (e.g., `files:///foo/bar`).
+
+Supported formats are:
+
+include::../object-storage-format.adoc[]
+
+--
++
+Note that this setting should only be set by linkgit:git-init[1] or
+linkgit:git-clone[1]. Trying to change it after initialization will make
+preexisting objects inaccessible.
++
+NOTE: This extension is still considered EXPERIMENTAL and may be subject to
+change.
+
 refStorage:::
 	Specify the ref storage format and a corresponding payload. The value
 	can be either a format name or a URI:
diff --git a/Documentation/object-storage-format.adoc b/Documentation/object-storage-format.adoc
new file mode 100644
index 0000000000..4c4aef6dda
--- /dev/null
+++ b/Documentation/object-storage-format.adoc
@@ -0,0 +1,6 @@
+`files[://<path>]`;; for loose objects and packfiles. This is the default.
++
+The optional payload can be passed to specify an alternative filesystem path
+where the objects will be stored. Relative paths are resolved relative to the
+`$GIT_COMMON_DIR`. Defaults to `$GIT_COMMON_DIR/objects` when no payload is
+provided. The payload is ignored when `GIT_OBJECT_DIRECTORY` is set.
diff --git a/odb.c b/odb.c
index 98c5c862f5..6c07b3b671 100644
--- a/odb.c
+++ b/odb.c
@@ -757,7 +757,7 @@ struct object_database *odb_new(struct repository *repo,
 	o->repo = repo;
 	pthread_mutex_init(&o->replace_mutex, NULL);
 
-	o->source = odb_source_new(o, flags);
+	o->source = odb_source_new(o, repo->object_storage_format, flags);
 	o->inmemory_objects = &odb_source_inmemory_new(o)->base;
 
 	return o;
diff --git a/odb/source-files.c b/odb/source-files.c
index 455f409db0..4a7cbbfd9b 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -1235,6 +1235,7 @@ struct odb_files_dir *odb_source_files_find_dir(struct object_database *odb, con
 }
 
 struct odb_source_files *odb_source_files_new(struct object_database *odb,
+					      const char *payload,
 					      enum odb_new_flags flags)
 {
 	struct odb_source_files *files;
@@ -1245,6 +1246,12 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
 		object_dir = xstrdup_or_null(getenv(DB_ENVIRONMENT));
 		alternates = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));
 	}
+	if (!object_dir && payload) {
+		if (is_absolute_path(payload))
+			object_dir = xstrdup(payload);
+		else
+			object_dir = xstrfmt("%s/%s", odb->repo->commondir, payload);
+	}
 	if (!object_dir)
 		object_dir = xstrfmt("%s/objects", odb->repo->commondir);
 
diff --git a/odb/source-files.h b/odb/source-files.h
index 837e890304..71e8db84ff 100644
--- a/odb/source-files.h
+++ b/odb/source-files.h
@@ -70,6 +70,7 @@ struct odb_source_files {
 
 /* Allocate and initialize a new object source. */
 struct odb_source_files *odb_source_files_new(struct object_database *odb,
+					      const char *payload,
 					      enum odb_new_flags flags);
 
 /*
diff --git a/odb/source.c b/odb/source.c
index b25ef14df8..10abe33a26 100644
--- a/odb/source.c
+++ b/odb/source.c
@@ -1,4 +1,5 @@
 #include "git-compat-util.h"
+#include "gettext.h"
 #include "object-file.h"
 #include "odb/source-files.h"
 #include "odb/source.h"
@@ -23,10 +24,55 @@ const char *odb_source_type_to_name(enum odb_source_type type)
 	return name;
 }
 
+static enum odb_source_type odb_source_type_by_name(const char *name)
+{
+	if (!strcmp(name, "files"))
+		return ODB_SOURCE_FILES;
+	return ODB_SOURCE_UNKNOWN;
+}
+
+enum odb_source_type odb_source_type_by_uri(const char *uri, char **payload)
+{
+	enum odb_source_type type;
+	const char *schema_end;
+	char *name;
+
+	schema_end = strstr(uri, "://");
+	if (!schema_end) {
+		name = xstrdup(uri);
+		if (payload)
+			*payload = NULL;
+	} else {
+		name = xstrndup(uri, schema_end - uri);
+		if (payload)
+			*payload = *(schema_end + 3) ? xstrdup(schema_end + 3) : NULL;
+	}
+
+	type = odb_source_type_by_name(name);
+	free(name);
+	return type;
+}
+
 struct odb_source *odb_source_new(struct object_database *odb,
+				  const char *object_storage_format,
 				  enum odb_new_flags flags)
 {
-	return &odb_source_files_new(odb, flags)->base;
+	struct odb_source *source;
+	char *payload = NULL;
+
+	if (!object_storage_format)
+		return &odb_source_files_new(odb, NULL, flags)->base;
+
+	switch (odb_source_type_by_uri(object_storage_format, &payload)) {
+	case ODB_SOURCE_FILES:
+		source = &odb_source_files_new(odb, payload, flags)->base;
+		break;
+	default:
+		die(_("unknown object storage format '%s'"), object_storage_format);
+	}
+
+	free(payload);
+	return source;
 }
 
 void odb_source_init(struct odb_source *source,
diff --git a/odb/source.h b/odb/source.h
index 1867347dfc..c194b3254b 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -32,6 +32,14 @@ enum odb_source_type {
  */
 const char *odb_source_type_to_name(enum odb_source_type type);
 
+/*
+ * Parse the given URI in the format "<schema>[://<payload>]" and return the
+ * source type identified by the schema. If `payload` is non-NULL it will be
+ * set to a newly allocated copy of the payload, or NULL if the URI has no or
+ * an empty payload. Returns `ODB_SOURCE_UNKNOWN` for unknown schemas.
+ */
+enum odb_source_type odb_source_type_by_uri(const char *uri, char **payload);
+
 struct object_id;
 struct odb_stream;
 struct strbuf;
@@ -304,9 +312,11 @@ struct odb_source {
 
 /*
  * Allocate and initialize a new source for the given object database. The path
- * of the source is derived from repository paths.
+ * of the source is derived from repository paths or from the object storage
+ * format.
  */
 struct odb_source *odb_source_new(struct object_database *odb,
+				  const char *object_storage_format,
 				  enum odb_new_flags flags);
 
 /*
diff --git a/repository.c b/repository.c
index 970d181828..bb01fe5dd6 100644
--- a/repository.c
+++ b/repository.c
@@ -217,6 +217,13 @@ void repo_set_ref_storage_format(struct repository *repo,
 	repo->ref_storage_payload = xstrdup_or_null(payload);
 }
 
+void repo_set_object_storage_format(struct repository *repo,
+				    const char *uri)
+{
+	free(repo->object_storage_format);
+	repo->object_storage_format = xstrdup_or_null(uri);
+}
+
 /*
  * Attempt to resolve and set the provided 'gitdir' for repository 'repo'.
  * Return 0 upon success and a non-zero value upon failure.
@@ -382,6 +389,7 @@ void repo_clear(struct repository *repo)
 	free(repo->worktree);
 	free(repo->submodule_prefix);
 	free(repo->ref_storage_payload);
+	free(repo->object_storage_format);
 
 	odb_free(repo->objects);
 
diff --git a/repository.h b/repository.h
index 2a348012e8..e9a3a7f18b 100644
--- a/repository.h
+++ b/repository.h
@@ -178,6 +178,12 @@ struct repository {
 	 */
 	char *ref_storage_payload;
 
+	/*
+	 * The object storage URI in the format "<schema>[://<payload>]", as
+	 * configured via "extensions.objectStorage".
+	 */
+	char *object_storage_format;
+
 	/* A unique-id for tracing purposes. */
 	int trace2_repo_id;
 
@@ -255,6 +261,8 @@ void repo_set_compat_hash_algo(struct repository *repo, uint32_t compat_algo);
 void repo_set_ref_storage_format(struct repository *repo,
 				 enum ref_storage_format format,
 				 const char *payload);
+void repo_set_object_storage_format(struct repository *repo,
+				    const char *uri);
 void initialize_repository(struct repository *repo);
 RESULT_MUST_BE_USED
 int repo_init(struct repository *r, const char *gitdir, const char *worktree);
diff --git a/setup.c b/setup.c
index 3ee19f3e3c..499b5bbe3c 100644
--- a/setup.c
+++ b/setup.c
@@ -9,6 +9,7 @@
 #include "hex.h"
 #include "object-file.h"
 #include "object-name.h"
+#include "odb/source.h"
 #include "refs.h"
 #include "repository.h"
 #include "config.h"
@@ -687,6 +688,15 @@ static enum extension_result handle_extension(const char *var,
 				     "extensions.refstorage", value);
 		data->ref_storage_format = format;
 		return EXTENSION_OK;
+	} else if (!strcmp(ext, "objectstorage")) {
+		if (!value)
+			return config_error_nonbool(var);
+		if (odb_source_type_by_uri(value, NULL) == ODB_SOURCE_UNKNOWN)
+			return error(_("invalid value for '%s': '%s'"),
+				     "extensions.objectstorage", value);
+		free(data->object_storage);
+		data->object_storage = xstrdup(value);
+		return EXTENSION_OK;
 	} else if (!strcmp(ext, "relativeworktrees")) {
 		data->relative_worktrees = git_config_bool(var, value);
 		return EXTENSION_OK;
@@ -694,6 +704,7 @@ static enum extension_result handle_extension(const char *var,
 		data->submodule_path_cfg = git_config_bool(var, value);
 		return EXTENSION_OK;
 	}
+
 	return EXTENSION_UNKNOWN;
 }
 
@@ -864,6 +875,7 @@ void clear_repository_format(struct repository_format *format)
 	free(format->work_tree);
 	free(format->partial_clone);
 	free(format->ref_storage_payload);
+	free(format->object_storage);
 	init_repository_format(format);
 }
 
@@ -1786,6 +1798,7 @@ int apply_repository_format(struct repository *repo,
 	repo_set_ref_storage_format(repo,
 				    format->ref_storage_format,
 				    format->ref_storage_payload);
+	repo_set_object_storage_format(repo, format->object_storage);
 	repo->repository_format_worktree_config =
 		format->worktree_config;
 	repo->repository_format_submodule_path_cfg =
diff --git a/setup.h b/setup.h
index 7a3efe5e48..9145aacc5f 100644
--- a/setup.h
+++ b/setup.h
@@ -192,6 +192,7 @@ struct repository_format {
 	char *ref_storage_payload;
 	int sparse_index;
 	char *work_tree;
+	char *object_storage;
 	struct string_list unknown_extensions;
 	struct string_list v1_only_extensions;
 };
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index f1d227f271..c7c464d3a2 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -839,6 +839,36 @@ test_expect_success 'init with --ref-storage-format=garbage' '
 	test_cmp expect err
 '
 
+test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage is not allowed with repo version 0' '
+	test_when_finished "rm -rf objstorage" &&
+	git init objstorage &&
+	git -C objstorage config set extensions.objectStorage files &&
+	test_must_fail git -C objstorage rev-parse 2>err &&
+	test_grep "repo version is 0, but v1-only extension found" err
+'
+
+test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with files backend' '
+	test_when_finished "rm -rf objstorage" &&
+	git init objstorage &&
+	git -C objstorage config set core.repositoryformatversion 1 &&
+	git -C objstorage config set extensions.objectStorage files &&
+	test_commit -C objstorage A &&
+	git -C objstorage rev-parse --verify HEAD
+'
+
+test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with unknown backend' '
+	test_when_finished "rm -rf objstorage" &&
+	git init objstorage &&
+	git -C objstorage config set core.repositoryformatversion 1 &&
+	git -C objstorage config set extensions.objectStorage garbage &&
+	test_must_fail git -C objstorage rev-parse 2>err &&
+	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage${SQ}" err &&
+
+	git config --file objstorage/.git/config extensions.objectStorage garbage://payload &&
+	test_must_fail git -C objstorage rev-parse 2>err &&
+	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage://payload${SQ}" err
+'
+
 test_expect_success MINGW 'core.hidedotfiles = false' '
 	git config --global core.hidedotfiles false &&
 	rm -rf newdir &&

-- 
2.56.0.269.g68944adf51.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH 3/7] setup: allow overriding object storage format via environment
  2026-10-19  8:55 [PATCH 0/7] odb: introduce object storage extension Patrick Steinhardt
  2026-10-19  8:55 ` [PATCH 1/7] loose: drop unused `repo_write_loose_object_map()` Patrick Steinhardt
  2026-10-19  8:55 ` [PATCH 2/7] setup: introduce object storage extension Patrick Steinhardt
@ 2026-10-19  8:55 ` Patrick Steinhardt
  2026-10-19 21:34   ` Karthik Nayak
  2026-10-19  8:55 ` [PATCH 4/7] builtin: expose the object storage format Patrick Steinhardt
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-19  8:55 UTC (permalink / raw)
  To: git

The ref storage format configured via "extensions.refStorage" can be
overridden with the GIT_REF_STORAGE_FORMAT environment variable.
Introduce the GIT_OBJECT_STORAGE_FORMAT environment variable that
overrides "extensions.objectStorage" in the same way.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git.adoc |  5 +++++
 environment.h          |  1 +
 setup.c                | 14 ++++++++++++++
 t/t0001-init.sh        | 26 ++++++++++++++++++++++++++
 4 files changed, 46 insertions(+)

diff --git a/Documentation/git.adoc b/Documentation/git.adoc
index 3abb4701c3..34812131bb 100644
--- a/Documentation/git.adoc
+++ b/Documentation/git.adoc
@@ -609,6 +609,11 @@ double-quotes and respecting backslash escapes. E.g., the value
 `GIT_REFERENCE_BACKEND`::
 	Deprecated alias of `GIT_REF_STORAGE_FORMAT`.
 
+`GIT_OBJECT_STORAGE_FORMAT`::
+	Specify which object storage format to use along with its URI.
+	See `extensions.objectStorage` option in linkgit:git-config[1] for more
+	details. Overrides the config variable when used.
+
 Git Commits
 ~~~~~~~~~~~
 `GIT_AUTHOR_NAME`::
diff --git a/environment.h b/environment.h
index b336459e90..c26ef92b52 100644
--- a/environment.h
+++ b/environment.h
@@ -45,6 +45,7 @@
 #define GIT_ATTR_SOURCE_ENVIRONMENT "GIT_ATTR_SOURCE"
 #define GIT_REFERENCE_BACKEND_ENVIRONMENT "GIT_REFERENCE_BACKEND"
 #define GIT_REF_STORAGE_FORMAT_ENVIRONMENT "GIT_REF_STORAGE_FORMAT"
+#define GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT "GIT_OBJECT_STORAGE_FORMAT"
 
 /*
  * Environment variable used to propagate the --no-advice global option to the
diff --git a/setup.c b/setup.c
index 499b5bbe3c..98140a7641 100644
--- a/setup.c
+++ b/setup.c
@@ -2073,6 +2073,7 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
 
 		if (startup_info->have_repository) {
 			struct strbuf err = STRBUF_INIT;
+			const char *object_storage_uri;
 			const char *ref_backend_uri;
 
 			/*
@@ -2091,6 +2092,19 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
 					die(_("unknown ref storage format: '%s'"), ref_backend_uri);
 			}
 
+			/*
+			 * Likewise, the GIT_OBJECT_STORAGE_FORMAT environment
+			 * variable should override the repository config for
+			 * 'extensions.objectStorage'.
+			 */
+			object_storage_uri = getenv(GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT);
+			if (object_storage_uri) {
+				if (odb_source_type_by_uri(object_storage_uri, NULL) == ODB_SOURCE_UNKNOWN)
+					die(_("unknown object storage format: '%s'"), object_storage_uri);
+				free(discovery.format.object_storage);
+				discovery.format.object_storage = xstrdup(object_storage_uri);
+			}
+
 			if (apply_repository_format(repo, &discovery.format,
 						    APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)
 				die("%s", err.buf);
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index c7c464d3a2..5e7c25e42c 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -869,6 +869,32 @@ test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with unknown b
 	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage://payload${SQ}" err
 '
 
+test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FORMAT overrides extensions.objectStorage' '
+	test_when_finished "rm -rf objstorage" &&
+	git init objstorage &&
+	(
+		cd objstorage &&
+		git config set core.repositoryformatversion 1 &&
+		git config set extensions.objectStorage files://objs &&
+		mkdir .git/objs .git/other &&
+
+		echo content >file &&
+		oid=$(git hash-object -w file) &&
+		test_path_is_file .git/objs/$(test_oid_to_path "$oid") &&
+		git cat-file -e "$oid" &&
+
+		test_must_fail env GIT_OBJECT_STORAGE_FORMAT=files://other git cat-file -e "$oid" &&
+		GIT_OBJECT_STORAGE_FORMAT=files://other git hash-object -w file &&
+		test_path_is_file .git/other/$(test_oid_to_path "$oid") &&
+
+		cat >expect <<-EOF &&
+		fatal: unknown object storage format: ${SQ}garbage${SQ}
+		EOF
+		test_must_fail env GIT_OBJECT_STORAGE_FORMAT=garbage git rev-parse 2>err &&
+		test_cmp expect err
+	)
+'
+
 test_expect_success MINGW 'core.hidedotfiles = false' '
 	git config --global core.hidedotfiles false &&
 	rm -rf newdir &&

-- 
2.56.0.269.g68944adf51.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH 4/7] builtin: expose the object storage format
  2026-10-19  8:55 [PATCH 0/7] odb: introduce object storage extension Patrick Steinhardt
                   ` (2 preceding siblings ...)
  2026-10-19  8:55 ` [PATCH 3/7] setup: allow overriding object storage format via environment Patrick Steinhardt
@ 2026-10-19  8:55 ` Patrick Steinhardt
  2026-10-19 21:40   ` Karthik Nayak
  2026-10-19  8:56 ` [PATCH 5/7] builtin/init: introduce handling of " Patrick Steinhardt
                   ` (4 subsequent siblings)
  8 siblings, 1 reply; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-19  8:55 UTC (permalink / raw)
  To: git

The ref storage format of a repository can be inspected via both
git-rev-parse(1)'s "--show-ref-storage-format" and git-repo(1)'s
"references.storageFormat" field. Introduce the equivalent options for
the object storage format, as well.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-repo.adoc      |  6 ++++++
 Documentation/git-rev-parse.adoc |  5 +++++
 builtin/repo.c                   | 10 ++++++++++
 builtin/rev-parse.c              |  7 +++++++
 t/t0001-init.sh                  |  5 ++++-
 t/t1900-repo-info.sh             |  3 +++
 6 files changed, 35 insertions(+), 1 deletion(-)

diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index cb8c1e7292..d2dc9b7a69 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -105,6 +105,12 @@ values that they return:
 `object.format`::
 	The object format (hash algorithm) used in the repository.
 
+`objects.storageFormat`::
+	The object storage format used in the repository, as configured via
+	`extensions.objectStorage`. The valid values are:
++
+include::object-storage-format.adoc[]
+
 `path.commondir.absolute`::
 	The canonical absolute path to the Git repository's common
 	directory (the shared `.git` directory containing objects,
diff --git a/Documentation/git-rev-parse.adoc b/Documentation/git-rev-parse.adoc
index 5b866af66a..9103d54d53 100644
--- a/Documentation/git-rev-parse.adoc
+++ b/Documentation/git-rev-parse.adoc
@@ -338,6 +338,11 @@ The following options are unaffected by `--path-format`:
 --show-ref-format::
 	Deprecated alias of `--show-ref-storage-format`.
 
+--show-object-storage-format::
+	Show the object storage format used for the repository. Prints the
+	value of `extensions.objectStorage`, or `files` when the extension is
+	not set.
+
 
 Other Options
 ~~~~~~~~~~~~~
diff --git a/builtin/repo.c b/builtin/repo.c
index 84e012f83f..da11a56ede 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -6,6 +6,7 @@
 #include "hash.h"
 #include "hex.h"
 #include "odb.h"
+#include "odb/source.h"
 #include "parse-options.h"
 #include "path.h"
 #include "path-walk.h"
@@ -128,11 +129,20 @@ static int get_references_format(struct repository *repo, struct strbuf *buf)
 	return 0;
 }
 
+static int get_object_storage_format(struct repository *repo, struct strbuf *buf)
+{
+	strbuf_addstr(buf, repo->object_storage_format ?
+		      repo->object_storage_format :
+		      odb_source_type_to_name(ODB_SOURCE_FILES));
+	return 0;
+}
+
 /* repo_info_field keys must be in lexicographical order */
 static const struct repo_info_field repo_info_field[] = {
 	{ "layout.bare", get_layout_bare },
 	{ "layout.shallow", get_layout_shallow },
 	{ "object.format", get_object_format },
+	{ "objects.storageFormat", get_object_storage_format },
 	{ "path.commondir.absolute", get_path_commondir_absolute },
 	{ "path.commondir.relative", get_path_commondir_relative },
 	{ "path.gitdir.absolute", get_path_gitdir_absolute },
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index ec33c19bdf..b206944ad9 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -17,6 +17,7 @@
 #include "gettext.h"
 #include "hash.h"
 #include "hex.h"
+#include "odb/source.h"
 #include "refs.h"
 #include "quote.h"
 #include "object-name.h"
@@ -1138,6 +1139,12 @@ int cmd_rev_parse(int argc,
 				puts(ref_storage_format_to_name(the_repository->ref_storage_format));
 				continue;
 			}
+			if (!strcmp(arg, "--show-object-storage-format")) {
+				puts(the_repository->object_storage_format ?
+				     the_repository->object_storage_format :
+				     odb_source_type_to_name(ODB_SOURCE_FILES));
+				continue;
+			}
 			if (!strcmp(arg, "--end-of-options")) {
 				seen_end_of_options = 1;
 				if (filter & (DO_FLAGS | DO_REVS))
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 5e7c25e42c..d080ab04a8 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -853,7 +853,10 @@ test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with files bac
 	git -C objstorage config set core.repositoryformatversion 1 &&
 	git -C objstorage config set extensions.objectStorage files &&
 	test_commit -C objstorage A &&
-	git -C objstorage rev-parse --verify HEAD
+	git -C objstorage rev-parse --verify HEAD &&
+	echo files >expect &&
+	git -C objstorage rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with unknown backend' '
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index d115d2d9f9..493a78f230 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -69,6 +69,9 @@ test_repo_info 'object.format = sha1 is retrieved correctly' \
 test_repo_info 'object.format = sha256 is retrieved correctly' \
 	'git init --object-format=sha256' 'sha256' 'object.format' 'sha256'
 
+test_repo_info 'objects.storageFormat = files is retrieved correctly' \
+	'git init' 'objects-files' 'objects.storageFormat' 'files'
+
 test_expect_success 'values returned in order requested' '
 	cat >expect <<-\EOF &&
 	layout.bare=false

-- 
2.56.0.269.g68944adf51.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH 5/7] builtin/init: introduce handling of object storage format
  2026-10-19  8:55 [PATCH 0/7] odb: introduce object storage extension Patrick Steinhardt
                   ` (3 preceding siblings ...)
  2026-10-19  8:55 ` [PATCH 4/7] builtin: expose the object storage format Patrick Steinhardt
@ 2026-10-19  8:56 ` Patrick Steinhardt
  2026-10-19  8:56 ` [PATCH 6/7] builtin/clone: introduce "--object-storage-format=" Patrick Steinhardt
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-19  8:56 UTC (permalink / raw)
  To: git

While the object storage format extension can already be configured
manually via git-config(1), this isn't exactly an ergonomic way of doing
so. For the reference storage format extension, users have a host of
options available to them.

Introduce the equivalent options for the object storage format.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/config/init.adoc |   5 ++
 Documentation/git-init.adoc    |  10 ++-
 Documentation/git.adoc         |   5 ++
 builtin/clone.c                |   2 +-
 builtin/init-db.c              |   7 +-
 setup.c                        |  89 +++++++++++++++++++++-
 setup.h                        |   1 +
 t/t0001-init.sh                | 162 +++++++++++++++++++++++++++++++++++++++++
 8 files changed, 273 insertions(+), 8 deletions(-)

diff --git a/Documentation/config/init.adoc b/Documentation/config/init.adoc
index e45bd692db..dbd9c663ee 100644
--- a/Documentation/config/init.adoc
+++ b/Documentation/config/init.adoc
@@ -20,6 +20,11 @@ endif::[]
 	precedence over this config.
 `init.defaultRefFormat`::
 	Deprecated alias of `init.defaultRefStorageFormat`.
+`init.defaultObjectStorageFormat`::
+	Allows overriding the default object storage format for new
+	repositories. See `--object-storage-format=` in linkgit:git-init[1].
+	Both the command line option and the `GIT_DEFAULT_OBJECT_STORAGE_FORMAT`
+	environment variable take precedence over this config.
 
 init.defaultSubmodulePathConfig::
 	A boolean that specifies if `git init` and `git clone` should
diff --git a/Documentation/git-init.adoc b/Documentation/git-init.adoc
index b02af22ad1..9e022a7ba8 100644
--- a/Documentation/git-init.adoc
+++ b/Documentation/git-init.adoc
@@ -11,7 +11,7 @@ SYNOPSIS
 [synopsis]
 git init [-q | --quiet] [--bare] [--template=<template-directory>]
 	 [--separate-git-dir <git-dir>] [--object-format=<format>]
-	 [--ref-storage-format=<format>]
+	 [--ref-storage-format=<format>] [--object-storage-format=<format>]
 	 [-b <branch-name> | --initial-branch=<branch-name>]
 	 [--shared[=<permissions>]] [<directory>]
 
@@ -68,6 +68,14 @@ include::ref-storage-format.adoc[]
 `--ref-format=<format>`::
 Deprecated alias of `--ref-storage-format=<format>`.
 
+`--object-storage-format=<format>`::
+Specify the given object storage _<format>_ for the repository. Backends that
+require additional configuration accept a payload in the form
+`<format>://<payload>`, for example a path or a connection string identifying
+the database that shall store the objects. The valid values are:
++
+include::object-storage-format.adoc[]
+
 `--template=<template-directory>`::
 Specify the directory from which templates will be used.  (See the "TEMPLATE
 DIRECTORY" section below.)
diff --git a/Documentation/git.adoc b/Documentation/git.adoc
index 34812131bb..67de5646f0 100644
--- a/Documentation/git.adoc
+++ b/Documentation/git.adoc
@@ -609,6 +609,11 @@ double-quotes and respecting backslash escapes. E.g., the value
 `GIT_REFERENCE_BACKEND`::
 	Deprecated alias of `GIT_REF_STORAGE_FORMAT`.
 
+`GIT_DEFAULT_OBJECT_STORAGE_FORMAT`::
+	If this variable is set, the default object storage format for new
+	repositories will be set to this value. The default is "files".
+	See `--object-storage-format` in linkgit:git-init[1].
+
 `GIT_OBJECT_STORAGE_FORMAT`::
 	Specify which object storage format to use along with its URI.
 	See `extensions.objectStorage` option in linkgit:git-config[1] for more
diff --git a/builtin/clone.c b/builtin/clone.c
index 6047501dfc..ba4fc16def 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1214,7 +1214,7 @@ int cmd_clone(int argc,
 	 */
 	create_repository(the_repository, git_dir, real_git_dir, work_tree,
 			  option_template, GIT_HASH_UNKNOWN, ref_storage_format_uri,
-			  do_not_override_repo_unix_permissions, NULL);
+			  NULL, do_not_override_repo_unix_permissions, NULL);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index 0abdbca8e9..71b748f2f2 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -57,7 +57,7 @@ static int shared_callback(const struct option *opt, const char *arg, int unset)
 static const char *const init_db_usage[] = {
 	N_("git init [-q | --quiet] [--bare] [--template=<template-directory>]\n"
 	   "         [--separate-git-dir <git-dir>] [--object-format=<format>]\n"
-	   "         [--ref-storage-format=<format>]\n"
+	   "         [--ref-storage-format=<format>] [--object-storage-format=<format>]\n"
 	   "         [-b <branch-name> | --initial-branch=<branch-name>]\n"
 	   "         [--shared[=<permissions>]] [<directory>]"),
 	NULL
@@ -84,6 +84,7 @@ int cmd_init_db(int argc,
 	int bare = startup_info->force_bare_repository ? 1 : -1;
 	const char *object_format = NULL;
 	const char *ref_storage_format_uri = NULL;
+	const char *object_storage_format_uri = NULL;
 	const char *initial_branch = NULL;
 	int hash_algo = GIT_HASH_UNKNOWN;
 	int init_shared_repository = -1;
@@ -111,6 +112,8 @@ int cmd_init_db(int argc,
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format_uri, N_("format"),
 			   N_("specify the reference storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
+		OPT_STRING(0, "object-storage-format", &object_storage_format_uri, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_END()
 	};
 	int reinit;
@@ -232,7 +235,7 @@ int cmd_init_db(int argc,
 
 	create_repository(the_repository, git_dir, real_git_dir, work_tree,
 			  template_dir, hash_algo, ref_storage_format_uri,
-			  init_shared_repository, &reinit);
+			  object_storage_format_uri, init_shared_repository, &reinit);
 	create_reference_database(the_repository, initial_branch, quiet);
 	create_object_database(the_repository, NULL);
 
diff --git a/setup.c b/setup.c
index 98140a7641..7117336b13 100644
--- a/setup.c
+++ b/setup.c
@@ -2466,7 +2466,8 @@ void initialize_repository_version(struct repository *repo,
 	 */
 	if (hash_algo != GIT_HASH_SHA1_LEGACY ||
 	    ref_storage_format != REF_STORAGE_FORMAT_FILES ||
-	    repo->ref_storage_payload)
+	    repo->ref_storage_payload ||
+	    repo->object_storage_format)
 		target_version = GIT_REPO_VERSION_READ;
 
 	if (hash_algo != GIT_HASH_SHA1_LEGACY && hash_algo != GIT_HASH_UNKNOWN)
@@ -2490,6 +2491,12 @@ void initialize_repository_version(struct repository *repo,
 		repo_config_set_gently(repo, "extensions.refstorage", NULL);
 	}
 
+	if (repo->object_storage_format)
+		repo_config_set(repo, "extensions.objectstorage",
+				repo->object_storage_format);
+	else if (reinit)
+		repo_config_set_gently(repo, "extensions.objectstorage", NULL);
+
 	if (reinit) {
 		struct strbuf config = STRBUF_INIT;
 		struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
@@ -2726,6 +2733,7 @@ static void separate_git_dir(struct repository *repo,
 struct default_format_config {
 	int hash;
 	enum ref_storage_format ref_storage_format;
+	char *object_storage_format;
 };
 
 static int read_default_format_config(const char *key, const char *value,
@@ -2757,6 +2765,20 @@ static int read_default_format_config(const char *key, const char *value,
 		goto out;
 	}
 
+	if (!strcmp(key, "init.defaultobjectstorageformat")) {
+		ret = git_config_string(&str, key, value);
+		if (ret)
+			goto out;
+		if (odb_source_type_by_uri(str, NULL) == ODB_SOURCE_UNKNOWN) {
+			warning(_("unknown object storage format '%s'"), str);
+			goto out;
+		}
+		free(cfg->object_storage_format);
+		cfg->object_storage_format = str;
+		str = NULL;
+		goto out;
+	}
+
 	/*
 	 * Enable the reftable format when "features.experimental" is enabled.
 	 * "init.defaultRefStorageFormat" takes precedence over this setting.
@@ -2777,7 +2799,8 @@ static int read_default_format_config(const char *key, const char *value,
 
 static void repository_format_configure(struct repository_format *repo_fmt,
 					int hash,
-					const char *ref_storage_format_uri)
+					const char *ref_storage_format_uri,
+					const char *object_storage_uri)
 {
 	struct default_format_config cfg = {
 		.hash = GIT_HASH_UNKNOWN,
@@ -2790,6 +2813,7 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 	};
 	enum ref_storage_format ref_storage_format;
 	char *ref_storage_payload = NULL;
+	char *object_storage = NULL;
 	const char *env;
 
 	config_with_options(read_default_format_config, &cfg, NULL, NULL, &opts);
@@ -2877,9 +2901,58 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 		ref_storage_format = REF_STORAGE_FORMAT_DEFAULT;
 	}
 
+	/*
+	 * Determine the object storage format, with the same precedence as
+	 * for the ref storage format:
+	 *
+	 *   1. The format specified via the command line.
+	 *
+	 *   2. The format specified via "GIT_OBJECT_STORAGE_FORMAT".
+	 *
+	 *   3. The format of a preexisting repository when reinitializing.
+	 *
+	 *   4. The default format for new repositories as configured via
+	 *      "GIT_DEFAULT_OBJECT_STORAGE_FORMAT".
+	 *
+	 *   5. The default format for new repositories as configured via
+	 *      "init.defaultObjectStorageFormat".
+	 *
+	 *   6. The "files" format.
+	 */
+	if (object_storage_uri) {
+		if (odb_source_type_by_uri(object_storage_uri, NULL) == ODB_SOURCE_UNKNOWN)
+			die(_("unknown object storage format '%s'"), object_storage_uri);
+		object_storage = xstrdup(object_storage_uri);
+	} else if ((env = getenv(GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT))) {
+		if (odb_source_type_by_uri(env, NULL) == ODB_SOURCE_UNKNOWN)
+			die(_("unknown object storage format specified via %s: '%s'"),
+			    GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT, env);
+		object_storage = xstrdup(env);
+	} else if (repo_fmt->version >= 0) {
+		object_storage = xstrdup_or_null(repo_fmt->object_storage);
+	} else if ((env = getenv("GIT_DEFAULT_OBJECT_STORAGE_FORMAT"))) {
+		if (odb_source_type_by_uri(env, NULL) == ODB_SOURCE_UNKNOWN)
+			die(_("unknown object storage format specified via %s: '%s'"),
+			    "GIT_DEFAULT_OBJECT_STORAGE_FORMAT", env);
+		object_storage = xstrdup(env);
+	} else {
+		object_storage = xstrdup_or_null(cfg.object_storage_format);
+	}
+
+	/*
+	 * The "files" format without a payload is the default and thus doesn't
+	 * need to be recorded as an extension.
+	 */
+	if (object_storage) {
+		char *payload = NULL;
+		if (odb_source_type_by_uri(object_storage, &payload) == ODB_SOURCE_FILES && !payload)
+			FREE_AND_NULL(object_storage);
+		free(payload);
+	}
+
 	/*
 	 * If we have a preexisting repository we need to verify that its
-	 * current ref storage format does not change.
+	 * current storage formats do not change.
 	 */
 	if (repo_fmt->version >= 0) {
 		if (ref_storage_format != repo_fmt->ref_storage_format)
@@ -2888,11 +2961,18 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 		    strcmp(ref_storage_payload ? ref_storage_payload : "",
 			   repo_fmt->ref_storage_payload ? repo_fmt->ref_storage_payload : ""))
 			die(_("attempt to reinitialize repository with different reference storage payload"));
+		if (strcmp(object_storage ? object_storage : "",
+			   repo_fmt->object_storage ? repo_fmt->object_storage : ""))
+			die(_("attempt to reinitialize repository with different object storage format"));
 	}
 
 	free(repo_fmt->ref_storage_payload);
 	repo_fmt->ref_storage_format = ref_storage_format;
 	repo_fmt->ref_storage_payload = ref_storage_payload;
+
+	free(repo_fmt->object_storage);
+	repo_fmt->object_storage = object_storage;
+	free(cfg.object_storage_format);
 }
 
 void create_repository(struct repository *repo,
@@ -2902,6 +2982,7 @@ void create_repository(struct repository *repo,
 		       const char *template_dir,
 		       int hash,
 		       const char *ref_storage_format_uri,
+		       const char *object_storage_format_uri,
 		       int init_shared_repository,
 		       int *reinit_ok)
 {
@@ -2940,7 +3021,7 @@ void create_repository(struct repository *repo,
 	 * is an attempt to reinitialize new repository with an old tool.
 	 */
 	read_and_verify_repository_format(&repo_fmt, repo_get_git_dir(repo), NULL);
-	repository_format_configure(&repo_fmt, hash, ref_storage_format_uri);
+	repository_format_configure(&repo_fmt, hash, ref_storage_format_uri, object_storage_format_uri);
 	if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)
 		die("%s", err.buf);
 
diff --git a/setup.h b/setup.h
index 9145aacc5f..4e1737ade9 100644
--- a/setup.h
+++ b/setup.h
@@ -275,6 +275,7 @@ void create_repository(struct repository *repo,
 		       const char *template_dir,
 		       int hash_algo,
 		       const char *ref_storage_format_uri,
+		       const char *object_storage_format_uri,
 		       int init_shared_repository,
 		       int *reinit_ok);
 
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index d080ab04a8..0da14e8f70 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -898,6 +898,168 @@ test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FORMAT overrides ext
 	)
 '
 
+test_expect_success 'init with --object-storage-format=files does not set extension' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files objformat &&
+	test_must_fail git -C objformat config get extensions.objectstorage &&
+	echo files >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual &&
+	if test_have_prereq DEFAULT_REPO_FORMAT
+	then
+		echo 0 >expect &&
+		git -C objformat config get core.repositoryformatversion >actual &&
+		test_cmp expect actual
+	fi
+'
+
+test_expect_success 'init with --object-storage-format=files:// does not set extension' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files:// objformat &&
+	test_must_fail git -C objformat config get extensions.objectstorage &&
+	echo files >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'init with --object-storage-format=files://<relative-path>' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files://objs objformat &&
+	echo files://objs >expect &&
+	git -C objformat config get extensions.objectstorage >actual &&
+	test_cmp expect actual &&
+	echo 1 >expect &&
+	git -C objformat config get core.repositoryformatversion >actual &&
+	test_cmp expect actual &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	echo files://objs >expect &&
+	test_cmp expect actual &&
+	test_path_is_dir objformat/.git/objects &&
+	test_path_is_dir objformat/.git/objs &&
+	test_commit -C objformat A &&
+	git -C objformat rev-parse --verify HEAD &&
+	test_path_is_missing objformat/.git/objects/pack &&
+	test_path_is_dir objformat/.git/objs/pack
+'
+
+test_expect_success 'init with --object-storage-format=files://<absolute-path>' '
+	test_when_finished "rm -rf objformat objdir" &&
+	git init --object-storage-format=files://"$(pwd)/objdir" objformat &&
+	echo "files://$(pwd)/objdir" >expect &&
+	git -C objformat config get extensions.objectstorage >actual &&
+	test_cmp expect actual &&
+	test_commit -C objformat A &&
+	git -C objformat rev-parse --verify HEAD &&
+	test_path_is_dir objdir/pack
+'
+
+test_expect_success 'GIT_OBJECT_DIRECTORY takes precedence over extensions.objectStorage payload' '
+	test_when_finished "rm -rf objformat objdir" &&
+	git init --object-storage-format=files://objs objformat &&
+	test_commit -C objformat A &&
+	mkdir objdir &&
+	test_must_fail env GIT_DIR="$PWD/objformat/.git" GIT_OBJECT_DIRECTORY="$PWD/objdir" \
+		git cat-file -e HEAD &&
+	GIT_DIR="$PWD/objformat/.git" GIT_OBJECT_DIRECTORY="$PWD/objdir" \
+		git hash-object -w objformat/A.t >oid &&
+	test_path_is_file objdir/$(test_oid_to_path "$(cat oid)")
+'
+
+test_expect_success 'init with --object-storage-format=garbage' '
+	test_when_finished "rm -rf objformat" &&
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format ${SQ}garbage${SQ}
+	EOF
+	test_must_fail git init --object-storage-format=garbage objformat 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'init with GIT_DEFAULT_OBJECT_STORAGE_FORMAT=garbage' '
+	test_when_finished "rm -rf objformat" &&
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format specified via GIT_DEFAULT_OBJECT_STORAGE_FORMAT: ${SQ}garbage${SQ}
+	EOF
+	test_must_fail env GIT_DEFAULT_OBJECT_STORAGE_FORMAT=garbage git init objformat 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'init with GIT_DEFAULT_OBJECT_STORAGE_FORMAT=files://objs' '
+	test_when_finished "rm -rf objformat" &&
+	GIT_DEFAULT_OBJECT_STORAGE_FORMAT=files://objs git init objformat &&
+	echo files://objs >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'init with GIT_OBJECT_STORAGE_FORMAT=garbage' '
+	test_when_finished "rm -rf objformat" &&
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format specified via GIT_OBJECT_STORAGE_FORMAT: ${SQ}garbage${SQ}
+	EOF
+	test_must_fail env GIT_OBJECT_STORAGE_FORMAT=garbage git init objformat 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'GIT_OBJECT_STORAGE_FORMAT takes precedence over default' '
+	test_when_finished "rm -rf objformat" &&
+	GIT_DEFAULT_OBJECT_STORAGE_FORMAT=files://default \
+	GIT_OBJECT_STORAGE_FORMAT=files://explicit git init objformat &&
+	echo files://explicit >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '--object-storage-format= takes precedence over environment' '
+	test_when_finished "rm -rf objformat" &&
+	GIT_OBJECT_STORAGE_FORMAT=files://explicit \
+	git init --object-storage-format=files://cmdline objformat &&
+	echo files://cmdline >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 're-init refuses with different storage format' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files objformat &&
+	cat >expect <<-EOF &&
+	fatal: attempt to reinitialize repository with different object storage format
+	EOF
+	test_must_fail env GIT_OBJECT_STORAGE_FORMAT=files://objs git init objformat 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 're-init with same object storage format' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files://objs objformat &&
+	git init --object-storage-format=files://objs objformat &&
+	git init objformat &&
+	echo files://objs >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'init warns about invalid init.defaultObjectStorageFormat' '
+	test_when_finished "rm -rf objformat" &&
+	test_config_global init.defaultObjectStorageFormat garbage &&
+	echo "warning: unknown object storage format ${SQ}garbage${SQ}" >expect &&
+	git init objformat 2>err &&
+	test_cmp expect err &&
+	echo files >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'init with init.defaultObjectStorageFormat' '
+	test_when_finished "rm -rf objformat" &&
+	test_config_global init.defaultObjectStorageFormat files://objs &&
+	git init objformat &&
+	echo files://objs >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual &&
+	test_commit -C objformat A &&
+	test_path_is_dir objformat/.git/objs/pack
+'
+
 test_expect_success MINGW 'core.hidedotfiles = false' '
 	git config --global core.hidedotfiles false &&
 	rm -rf newdir &&

-- 
2.56.0.269.g68944adf51.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH 6/7] builtin/clone: introduce "--object-storage-format="
  2026-10-19  8:55 [PATCH 0/7] odb: introduce object storage extension Patrick Steinhardt
                   ` (4 preceding siblings ...)
  2026-10-19  8:56 ` [PATCH 5/7] builtin/init: introduce handling of " Patrick Steinhardt
@ 2026-10-19  8:56 ` Patrick Steinhardt
  2026-10-19  8:56 ` [PATCH 7/7] builtin/submodule: propagate "--object-storage-format=" Patrick Steinhardt
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-19  8:56 UTC (permalink / raw)
  To: git

Wire up the new object storage format in git-clone(1) via a new
`--object-storage-format=` option, same as we have introduced in the
preceding commit for git-init(1).

Note that "--local" clones will not work if either of the object
databases is not using the "files" format. This is because we hardlink
loose objects and packfiles into the new repository, and that is
something that simply won't work with alternative backends.
Consequently, we either downgrade to a non-local clone with a warning,
or we die in case the user has explicitly asked for a local clone.

We _could_ in theory handle the case where the "files" backend has been
configured with an alternative object directory path (that is, with
"files:///path/to/dir"). But that's left for a future patch series to
implement for the sake of simplicity.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-clone.adoc | 11 +++++++
 builtin/clone.c              | 50 ++++++++++++++++++++++++++++++--
 t/t5601-clone.sh             | 68 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 127 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-clone.adoc b/Documentation/git-clone.adoc
index 07f7cf1a98..dd3b44817a 100644
--- a/Documentation/git-clone.adoc
+++ b/Documentation/git-clone.adoc
@@ -360,6 +360,17 @@ include::ref-storage-format.adoc[]
 `--ref-format=<format>`::
 Deprecated alias of `--ref-storage-format=<format>`.
 
+`--object-storage-format=<format>`::
+Specify the given object storage _<format>_ for the repository. Backends that
+require additional configuration accept a payload in the form
+`<format>://<payload>`, for example a path or a connection string identifying
+the database that shall store the objects. The valid values are:
++
+include::object-storage-format.adoc[]
++
+Local clones (see `--local`) fall back to the regular transport when either
+repository uses a non-default object storage.
+
 `-j<n>`::
 `--jobs=<n>`::
 	The number of submodules fetched at the same time.
diff --git a/builtin/clone.c b/builtin/clone.c
index ba4fc16def..85c124dc37 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -26,6 +26,7 @@
 #include "refspec.h"
 #include "object-file.h"
 #include "odb.h"
+#include "odb/source.h"
 #include "tree.h"
 #include "tree-walk.h"
 #include "unpack-trees.h"
@@ -378,6 +379,28 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 	dir_iterator_free(iter);
 }
 
+static bool is_using_default_object_storage_format(const char *repo_path)
+{
+	struct repository_format format = REPOSITORY_FORMAT_INIT;
+	struct strbuf config = STRBUF_INIT;
+	bool uses_default_format = true;
+	char *payload = NULL;
+
+	get_common_dir(&config, repo_path);
+	strbuf_addstr(&config, "/config");
+	read_repository_format(&format, config.buf);
+
+	if (format.version >= 1 &&
+	    format.object_storage &&
+	    (odb_source_type_by_uri(format.object_storage, &payload) != ODB_SOURCE_FILES || payload))
+		uses_default_format = false;
+
+	clear_repository_format(&format);
+	strbuf_release(&config);
+	free(payload);
+	return uses_default_format;
+}
+
 static void clone_local(const char *src_repo, const char *dest_repo)
 {
 	if (!option_shared) {
@@ -927,6 +950,7 @@ int cmd_clone(int argc,
 	struct string_list option_not = STRING_LIST_INIT_NODUP;
 	const char *real_git_dir = NULL;
 	const char *ref_storage_format_uri = NULL;
+	const char *object_storage_format_uri = NULL;
 	const char *option_upload_pack = "git-upload-pack";
 	int option_progress = -1;
 	int option_sparse_checkout = 0;
@@ -1008,6 +1032,8 @@ int cmd_clone(int argc,
 			   N_("separate git dir from working tree")),
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format_uri, N_("format"),
 			   N_("specify the reference storage format to use")),
+		OPT_STRING(0, "object-storage-format", &object_storage_format_uri, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
 		OPT_STRING_LIST('c', "config", &option_config, N_("key=value"),
 				N_("set config inside the new repository")),
@@ -1060,6 +1086,10 @@ int cmd_clone(int argc,
 			die(_("unknown ref storage format '%s'"), ref_storage_format_uri);
 	}
 
+	if (object_storage_format_uri &&
+	    odb_source_type_by_uri(object_storage_format_uri, NULL) == ODB_SOURCE_UNKNOWN)
+		die(_("unknown object storage format '%s'"), object_storage_format_uri);
+
 	if (option_mirror) {
 		option_bare = 1;
 		option_tags = 0;
@@ -1213,8 +1243,9 @@ int cmd_clone(int argc,
 	 * their on-disk data structures.
 	 */
 	create_repository(the_repository, git_dir, real_git_dir, work_tree,
-			  option_template, GIT_HASH_UNKNOWN, ref_storage_format_uri,
-			  NULL, do_not_override_repo_unix_permissions, NULL);
+			  option_template, GIT_HASH_UNKNOWN,
+			  ref_storage_format_uri, object_storage_format_uri,
+			  do_not_override_repo_unix_permissions, NULL);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
@@ -1362,6 +1393,21 @@ int cmd_clone(int argc,
 			is_local = 0;
 		}
 	}
+
+	/*
+	 * Local clones copy or hardlink the object directory of the source repository
+	 * into the destination. This only works when both repositories use the "files"
+	 * object storage format.
+	 */
+	if (is_local &&
+	    (!is_using_default_object_storage_format(path) || !is_using_default_object_storage_format(git_dir))) {
+		if (option_local > 0)
+			die(_("cannot perform local clone with non-default object storage format"));
+		else
+			warning(_("falling back to non-local clone because of non-default object storage format"));
+		is_local = 0;
+	}
+
 	if (option_local > 0 && !is_local)
 		warning(_("--local is ignored"));
 
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 06f6121f82..85f439d6b6 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -181,6 +181,74 @@ test_expect_success 'clone with garbage ref format' '
 	test_path_is_missing ref-storage
 '
 
+test_expect_success 'clone with files object storage format' '
+	test_when_finished "rm -rf object-storage" &&
+	git clone --object-storage-format=files --bare src object-storage &&
+	test_must_fail git -C object-storage config get extensions.objectstorage &&
+	git -C object-storage rev-parse --show-object-storage-format >actual &&
+	echo files >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'clone with object storage payload' '
+	test_when_finished "rm -rf object-storage" &&
+	git clone --object-storage-format=files://objs --bare src object-storage &&
+	git -C object-storage config get extensions.objectstorage >actual &&
+	echo files://objs >expect &&
+	test_cmp expect actual &&
+	git -C object-storage rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual &&
+	test_path_is_dir object-storage/objs/pack &&
+	test_path_is_missing object-storage/objects/pack
+'
+
+test_expect_success 'clone with object storage payload and bundle URI' '
+	test_when_finished "rm -rf object-storage src.bundle" &&
+	git -C src bundle create ../src.bundle --all &&
+	git clone --object-storage-format=files://objs --bundle-uri="$PWD/src.bundle" \
+		src object-storage &&
+	git -C object-storage rev-parse --show-object-storage-format >actual &&
+	echo files://objs >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'clone with garbage object storage format' '
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format ${SQ}garbage${SQ}
+	EOF
+	test_must_fail git clone --object-storage-format=garbage --bare src object-storage 2>err &&
+	test_cmp expect err &&
+	test_path_is_missing object-storage
+'
+
+test_expect_success 'local clone into non-default object storage bails' '
+	test_when_finished "rm -rf object-storage" &&
+	test_must_fail git clone --local --object-storage-format=files://objs src object-storage 2>err &&
+	test_grep "cannot perform local clone with non-default object storage format" err
+'
+
+test_expect_success 'local clone from explicit files object storage succeeds' '
+	test_when_finished "rm -rf object-storage object-storage-dest" &&
+	git clone src object-storage &&
+	git -C object-storage config set core.repositoryformatversion 1 &&
+	git -C object-storage config set extensions.objectstorage files &&
+	git clone --local object-storage object-storage-dest
+'
+
+test_expect_success 'local clone from non-default object storage bails' '
+	test_when_finished "rm -rf object-storage object-storage-dest" &&
+	git clone --object-storage-format=files://objs src object-storage &&
+	test_must_fail git clone --local object-storage object-storage-dest 2>err &&
+	test_grep "cannot perform local clone with non-default object storage format" err
+'
+
+test_expect_success 'local clone from non-default object storage without --local warns' '
+	test_when_finished "rm -rf object-storage object-storage-dest" &&
+	git clone --object-storage-format=files://objs src object-storage &&
+	git clone object-storage object-storage-dest 2>err &&
+	test_grep "falling back to non-local clone" err
+'
+
 test_expect_success 'clone to destination with trailing /' '
 
 	git clone src target-1/ &&

-- 
2.56.0.269.g68944adf51.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH 7/7] builtin/submodule: propagate "--object-storage-format="
  2026-10-19  8:55 [PATCH 0/7] odb: introduce object storage extension Patrick Steinhardt
                   ` (5 preceding siblings ...)
  2026-10-19  8:56 ` [PATCH 6/7] builtin/clone: introduce "--object-storage-format=" Patrick Steinhardt
@ 2026-10-19  8:56 ` Patrick Steinhardt
  2026-10-21  5:39 ` [PATCH v2 0/7] odb: introduce object storage extension Patrick Steinhardt
  2026-10-23 11:49 ` [PATCH v3 " Patrick Steinhardt
  8 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-19  8:56 UTC (permalink / raw)
  To: git

The ref storage format passed to git-clone(1) propagates to recursively
cloned submodules, and git-submodule(1)'s "add" and "update" subcommands
accept `--ref-storage-format=` so that newly cloned submodules can be
set up with a specific format.

Teach git-submodule(1) to also propagate the object storage format.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-submodule.adoc           | 10 +++-
 builtin/clone.c                            | 10 +++-
 builtin/submodule--helper.c                | 28 +++++++++
 git-submodule.sh                           | 18 ++++++
 t/meson.build                              |  1 +
 t/t7427-submodule-object-storage-format.sh | 91 ++++++++++++++++++++++++++++++
 6 files changed, 154 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-submodule.adoc b/Documentation/git-submodule.adoc
index e9c81a7170..57a21bc8d9 100644
--- a/Documentation/git-submodule.adoc
+++ b/Documentation/git-submodule.adoc
@@ -34,7 +34,7 @@ COMMANDS
 With no arguments, shows the status of existing submodules.  Several
 subcommands are available to perform operations on the submodules.
 
-`add [-b <branch>] [-f | --force] [--name <name>] [--reference <repository>] [--ref-storage-format <format>] [--depth <depth>] [--] <repository> [<path>]`::
+`add [-b <branch>] [-f | --force] [--name <name>] [--reference <repository>] [--ref-storage-format <format>] [--object-storage-format <format>] [--depth <depth>] [--] <repository> [<path>]`::
 	Add the given repository as a submodule at the given path
 	to the changeset to be committed next to the current
 	project: the current project is termed the "superproject".
@@ -75,6 +75,9 @@ URL in `.gitmodules`.
 If `--ref-storage-format <format>` or its deprecated alias `--ref-format
 <format>` is specified, the ref storage format of the newly cloned submodules
 will be set accordingly.
++
+If `--object-storage-format <format>` is specified, the object storage format
+of the newly cloned submodules will be set accordingly.
 
 `status [--cached] [--recursive] [--] [<path>...]`::
 	Show the status of the submodules. This will print the SHA-1 of the
@@ -140,7 +143,7 @@ If you really want to remove a submodule from the repository and commit
 that use linkgit:git-rm[1] instead. See linkgit:gitsubmodules[7] for removal
 options.
 
-`update [--init] [--remote] [-N | --no-fetch] [--[no-]recommend-shallow] [-f | --force] [--checkout | --rebase | --merge] [--reference=<repository>] [--ref-storage-format=<format>] [--depth=<depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter=<filter-spec>] [--] [<path>...]`::
+`update [--init] [--remote] [-N | --no-fetch] [--[no-]recommend-shallow] [-f | --force] [--checkout | --rebase | --merge] [--reference=<repository>] [--ref-storage-format=<format>] [--object-storage-format=<format>] [--depth=<depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter=<filter-spec>] [--] [<path>...]`::
 +
 --
 Update the registered submodules to match what the superproject
@@ -193,6 +196,9 @@ If `--ref-storage-format <format>` or its deprecated alias `--ref-format
 <format>` is specified, the ref storage format of the newly cloned submodules
 will be set accordingly.
 
+If `--object-storage-format <format>` is specified, the object storage format
+of the newly cloned submodules will be set accordingly.
+
 If `--filter <filter-spec>` is specified, the given partial clone filter will be
 applied to the submodule. See linkgit:git-rev-list[1] for details on filter
 specifications.
diff --git a/builtin/clone.c b/builtin/clone.c
index 85c124dc37..63348c3f34 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -686,7 +686,8 @@ static int git_sparse_checkout_init(const char *repo)
 static int checkout(int submodule_progress,
 		    struct list_objects_filter_options *filter_options,
 		    int filter_submodules,
-		    enum ref_storage_format ref_storage_format)
+		    enum ref_storage_format ref_storage_format,
+		    const char *object_storage_format_uri)
 {
 	struct object_id oid;
 	char *head;
@@ -776,6 +777,10 @@ static int checkout(int submodule_progress,
 			strvec_pushf(&cmd.args, "--ref-storage-format=%s",
 				     ref_storage_format_to_name(ref_storage_format));
 
+		if (object_storage_format_uri)
+			strvec_pushf(&cmd.args, "--object-storage-format=%s",
+				     object_storage_format_uri);
+
 		if (filter_submodules && filter_options->choice)
 			strvec_pushf(&cmd.args, "--filter=%s",
 				     expand_list_objects_filter_spec(filter_options));
@@ -1703,7 +1708,8 @@ int cmd_clone(int argc,
 	err = checkout(submodule_progress,
 		       &filter_options,
 		       filter_submodules,
-		       ref_storage_format);
+		       ref_storage_format,
+		       object_storage_format_uri);
 
 	list_objects_filter_release(&filter_options);
 
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 64412adf9f..8a9c9e17b6 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1739,6 +1739,7 @@ struct module_clone_data {
 	int depth;
 	struct list_objects_filter_options *filter_options;
 	enum ref_storage_format ref_storage_format;
+	const char *object_storage_format;
 	unsigned int quiet: 1;
 	unsigned int progress: 1;
 	unsigned int dissociate: 1;
@@ -1948,6 +1949,9 @@ static int clone_submodule(const struct module_clone_data *clone_data,
 		if (clone_data->ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
 			strvec_pushf(&cp.args, "--ref-storage-format=%s",
 				     ref_storage_format_to_name(clone_data->ref_storage_format));
+		if (clone_data->object_storage_format)
+			strvec_pushf(&cp.args, "--object-storage-format=%s",
+				     clone_data->object_storage_format);
 		if (clone_data->dissociate)
 			strvec_push(&cp.args, "--dissociate");
 		if (sm_gitdir && *sm_gitdir)
@@ -2065,6 +2069,8 @@ static int module_clone(int argc, const char **argv, const char *prefix,
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
 			   N_("specify the reference storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
+		OPT_STRING(0, "object-storage-format", &clone_data.object_storage_format, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_BOOL(0, "dissociate", &dissociate,
 			   N_("use --reference only while cloning")),
 		OPT_INTEGER(0, "depth", &clone_data.depth,
@@ -2095,6 +2101,9 @@ static int module_clone(int argc, const char **argv, const char *prefix,
 		if (clone_data.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
 			die(_("unknown ref storage format '%s'"), ref_storage_format);
 	}
+	if (clone_data.object_storage_format &&
+	    odb_source_type_by_uri(clone_data.object_storage_format, NULL) == ODB_SOURCE_UNKNOWN)
+		die(_("unknown object storage format '%s'"), clone_data.object_storage_format);
 	clone_data.dissociate = !!dissociate;
 	clone_data.quiet = !!quiet;
 	clone_data.progress = !!progress;
@@ -2202,6 +2211,7 @@ struct update_data {
 	struct list_objects_filter_options *filter_options;
 	struct module_list list;
 	enum ref_storage_format ref_storage_format;
+	const char *object_storage_format;
 	int depth;
 	int max_jobs;
 	int single_branch;
@@ -2365,6 +2375,9 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
 	if (suc->update_data->ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
 		strvec_pushf(&child->args, "--ref-storage-format=%s",
 			     ref_storage_format_to_name(suc->update_data->ref_storage_format));
+	if (suc->update_data->object_storage_format)
+		strvec_pushf(&child->args, "--object-storage-format=%s",
+			     suc->update_data->object_storage_format);
 	strvec_pushl(&child->args, "--path", sub->path, NULL);
 	strvec_pushl(&child->args, "--name", sub->name, NULL);
 	strvec_pushl(&child->args, "--url", url, NULL);
@@ -2809,6 +2822,9 @@ static void update_data_to_args(const struct update_data *update_data,
 	if (update_data->ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
 		strvec_pushf(args, "--ref-storage-format=%s",
 			     ref_storage_format_to_name(update_data->ref_storage_format));
+	if (update_data->object_storage_format)
+		strvec_pushf(args, "--object-storage-format=%s",
+			     update_data->object_storage_format);
 	if (update_data->filter_options && update_data->filter_options->choice)
 		strvec_pushf(args, "--filter=%s",
 				expand_list_objects_filter_spec(
@@ -3019,6 +3035,8 @@ static int module_update(int argc, const char **argv, const char *prefix,
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
 			   N_("specify the reference storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
+		OPT_STRING(0, "object-storage-format", &opt.object_storage_format, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_BOOL(0, "dissociate", &opt.dissociate,
 			   N_("use --reference only while cloning")),
 		OPT_INTEGER(0, "depth", &opt.depth,
@@ -3067,6 +3085,9 @@ static int module_update(int argc, const char **argv, const char *prefix,
 		if (opt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
 			die(_("unknown ref storage format '%s'"), ref_storage_format);
 	}
+	if (opt.object_storage_format &&
+	    odb_source_type_by_uri(opt.object_storage_format, NULL) == ODB_SOURCE_UNKNOWN)
+		die(_("unknown object storage format '%s'"), opt.object_storage_format);
 
 	opt.filter_options = &filter_options;
 	opt.prefix = prefix;
@@ -3372,6 +3393,7 @@ struct add_data {
 	const char *repo;
 	const char *realrepo;
 	enum ref_storage_format ref_storage_format;
+	const char *object_storage_format;
 	int depth;
 	unsigned int force: 1;
 	unsigned int quiet: 1;
@@ -3477,6 +3499,7 @@ static int add_submodule(const struct add_data *add_data)
 			string_list_append(&reference, p)->util = p;
 		}
 		clone_data.ref_storage_format = add_data->ref_storage_format;
+		clone_data.object_storage_format = add_data->object_storage_format;
 		clone_data.dissociate = add_data->dissociate;
 		if (add_data->depth >= 0)
 			clone_data.depth = add_data->depth;
@@ -3669,6 +3692,8 @@ static int module_add(int argc, const char **argv, const char *prefix,
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
 			   N_("specify the reference storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
+		OPT_STRING(0, "object-storage-format", &add_data.object_storage_format, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_BOOL(0, "dissociate", &dissociate, N_("borrow the objects from reference repositories")),
 		OPT_STRING(0, "name", &add_data.sm_name, N_("name"),
 			   N_("sets the submodule's name to the given string "
@@ -3700,6 +3725,9 @@ static int module_add(int argc, const char **argv, const char *prefix,
 		if (add_data.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
 			die(_("unknown ref storage format '%s'"), ref_storage_format);
 	}
+	if (add_data.object_storage_format &&
+	    odb_source_type_by_uri(add_data.object_storage_format, NULL) == ODB_SOURCE_UNKNOWN)
+		die(_("unknown object storage format '%s'"), add_data.object_storage_format);
 
 	add_data.repo = argv[0];
 	if (argc == 1)
diff --git a/git-submodule.sh b/git-submodule.sh
index 8632194138..4de5ffba04 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -103,6 +103,14 @@ cmd_add()
 		--ref-format=*|--ref-storage-format=*)
 			ref_storage_format="$1"
 			;;
+		--object-storage-format)
+			case "$2" in '') usage ;; esac
+			object_storage_format="--object-storage-format=$2"
+			shift
+			;;
+		--object-storage-format=*)
+			object_storage_format="$1"
+			;;
 		--dissociate)
 			dissociate=$1
 			;;
@@ -148,6 +156,7 @@ cmd_add()
 		${branch:+"$branch"} \
 		${reference:+"$reference"} \
 		${ref_storage_format:+"$ref_storage_format"} \
+		${object_storage_format:+"$object_storage_format"} \
 		$dissociate \
 		${name:+"$name"} \
 		${depth:+"$depth"} \
@@ -310,6 +319,14 @@ cmd_update()
 		--ref-format=*|--ref-storage-format=*)
 			ref_storage_format="$1"
 			;;
+		--object-storage-format)
+			case "$2" in '') usage ;; esac
+			object_storage_format="--object-storage-format=$2"
+			shift
+			;;
+		--object-storage-format=*)
+			object_storage_format="$1"
+			;;
 		--reference)
 			case "$2" in '') usage ;; esac
 			reference="--reference=$2"
@@ -386,6 +403,7 @@ cmd_update()
 		$merge \
 		$checkout \
 		${ref_storage_format:+"$ref_storage_format"} \
+		${object_storage_format:+"$object_storage_format"} \
 		${reference:+"$reference"} \
 		$dissociate \
 		${depth:+"$depth"} \
diff --git a/t/meson.build b/t/meson.build
index 11b3752f00..8542035b6f 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -926,6 +926,7 @@ integration_tests = [
   't7424-submodule-mixed-ref-formats.sh',
   't7425-submodule-gitdir-path-extension.sh',
   't7426-submodule-get-default-remote.sh',
+  't7427-submodule-object-storage-format.sh',
   't7450-bad-git-dotfiles.sh',
   't7500-commit-template-squash-signoff.sh',
   't7501-commit-basic-functionality.sh',
diff --git a/t/t7427-submodule-object-storage-format.sh b/t/t7427-submodule-object-storage-format.sh
new file mode 100755
index 0000000000..a2c9a5e753
--- /dev/null
+++ b/t/t7427-submodule-object-storage-format.sh
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+test_description='submodules handle the object storage format'
+
+. ./test-lib.sh
+
+test_object_storage_format () {
+	echo "$2" >expect &&
+	git -C "$1" rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'setup' '
+	git config set --global protocol.file.allow always
+'
+
+test_expect_success 'add existing repository with different object storage format' '
+	test_when_finished "rm -rf parent" &&
+
+	git init parent &&
+	(
+		cd parent &&
+		test_commit parent &&
+		git init --object-storage-format=files://objs submodule &&
+		test_commit -C submodule submodule &&
+		git submodule add ./submodule &&
+		test_object_storage_format submodule files://objs
+	)
+'
+
+test_expect_success 'add submodules with different object storage format' '
+	test_when_finished "rm -rf submodule upstream" &&
+
+	git init submodule &&
+	test_commit -C submodule submodule-initial &&
+	git init upstream &&
+	test_object_storage_format upstream files &&
+	git -C upstream submodule add --object-storage-format=files://objs "file://$(pwd)/submodule" &&
+	test_object_storage_format upstream/submodule files://objs &&
+	test_path_is_dir upstream/.git/modules/submodule/objs/pack
+'
+
+test_expect_success 'add submodules with garbage object storage format' '
+	test_when_finished "rm -rf submodule upstream" &&
+
+	git init submodule &&
+	test_commit -C submodule submodule-initial &&
+	git init upstream &&
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format ${SQ}garbage${SQ}
+	EOF
+	test_must_fail git -C upstream submodule add --object-storage-format=garbage "file://$(pwd)/submodule" 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'recursive clone propagates object storage format' '
+	test_when_finished "rm -rf submodule upstream downstream" &&
+
+	git init submodule &&
+	test_commit -C submodule submodule-initial &&
+	git init upstream &&
+	git -C upstream submodule add "file://$(pwd)/submodule" &&
+	git -C upstream commit -am "add submodule" &&
+
+	test_object_storage_format upstream files &&
+	test_object_storage_format upstream/submodule files &&
+
+	git clone --object-storage-format=files://objs --recurse-submodules \
+		upstream downstream &&
+	test_object_storage_format downstream files://objs &&
+	test_object_storage_format downstream/submodule files://objs &&
+	test_path_is_dir downstream/.git/objs/pack &&
+	test_path_is_dir downstream/.git/modules/submodule/objs/pack
+'
+
+test_expect_success 'clone submodules with different object storage format' '
+	test_when_finished "rm -rf submodule upstream downstream" &&
+
+	git init submodule &&
+	test_commit -C submodule submodule-initial &&
+	git init upstream &&
+	git -C upstream submodule add "file://$(pwd)/submodule" &&
+	git -C upstream commit -m "upstream submodule" &&
+
+	git clone --no-recurse-submodules "file://$(pwd)/upstream" downstream &&
+	git -C downstream submodule update --init --object-storage-format=files://objs &&
+	test_object_storage_format downstream files &&
+	test_object_storage_format downstream/submodule files://objs
+'
+
+test_done

-- 
2.56.0.269.g68944adf51.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* Re: [PATCH 1/7] loose: drop unused `repo_write_loose_object_map()`
  2026-10-19  8:55 ` [PATCH 1/7] loose: drop unused `repo_write_loose_object_map()` Patrick Steinhardt
@ 2026-10-19 20:21   ` Karthik Nayak
  2026-10-20  8:49     ` Patrick Steinhardt
  0 siblings, 1 reply; 49+ messages in thread
From: Karthik Nayak @ 2026-10-19 20:21 UTC (permalink / raw)
  To: Patrick Steinhardt, git

[-- Attachment #1: Type: text/plain, Size: 978 bytes --]

Patrick Steinhardt <ps@pks.im> writes:

> The function `repo_write_loose_object_map()` was introduced in
> 23b2c7e95b (loose: add a mapping between SHA-1 and SHA-256 for loose
> objects, 2023-10-01), but it never had any callers as loose object maps
> are instead written incrementally via `repo_add_loose_object_map()`
> whenever a new loose object is created.
>
> Besides being dead code, the function also hardcodes the location of the
> "loose-object-idx" file as "$GIT_COMMON_DIR/objects", whereas both the
> reader and `repo_add_loose_object_map()` derive the location from the
> loose source's path. The function would thus disagree with everyone else
> where the loose object map is supposed to be located when the object
> directory is using a non-default location.
>
> Remove the function.
>

It's like the function being unused is an insufficient argument by
itself, so we also talk about why its incorrect. Made me chuckle.

The patch obviously makes sense.

[snip]

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 2/7] setup: introduce object storage extension
  2026-10-19  8:55 ` [PATCH 2/7] setup: introduce object storage extension Patrick Steinhardt
@ 2026-10-19 21:29   ` Karthik Nayak
  2026-10-20  1:27     ` Junio C Hamano
  2026-10-20  8:50     ` Patrick Steinhardt
  0 siblings, 2 replies; 49+ messages in thread
From: Karthik Nayak @ 2026-10-19 21:29 UTC (permalink / raw)
  To: Patrick Steinhardt, git

[-- Attachment #1: Type: text/plain, Size: 3650 bytes --]

Patrick Steinhardt <ps@pks.im> writes:

[snip]

> diff --git a/odb/source-files.c b/odb/source-files.c
> index 455f409db0..4a7cbbfd9b 100644
> --- a/odb/source-files.c
> +++ b/odb/source-files.c
> @@ -1235,6 +1235,7 @@ struct odb_files_dir *odb_source_files_find_dir(struct object_database *odb, con
>  }
>
>  struct odb_source_files *odb_source_files_new(struct object_database *odb,
> +					      const char *payload,
>  					      enum odb_new_flags flags)
>  {
>  	struct odb_source_files *files;
> @@ -1245,6 +1246,12 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
>  		object_dir = xstrdup_or_null(getenv(DB_ENVIRONMENT));
>  		alternates = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));
>  	}
> +	if (!object_dir && payload) {
> +		if (is_absolute_path(payload))
> +			object_dir = xstrdup(payload);
> +		else
> +			object_dir = xstrfmt("%s/%s", odb->repo->commondir, payload);
> +	}

On the reference side, we use `strbuf_realpath()` to convert the
relative paths to absolute paths, should we be doing that here too?

>  	if (!object_dir)
>  		object_dir = xstrfmt("%s/objects", odb->repo->commondir);
>

[snip]

> diff --git a/odb/source.c b/odb/source.c
> index b25ef14df8..10abe33a26 100644
> --- a/odb/source.c
> +++ b/odb/source.c
> @@ -1,4 +1,5 @@
>  #include "git-compat-util.h"
> +#include "gettext.h"
>  #include "object-file.h"
>  #include "odb/source-files.h"
>  #include "odb/source.h"

Do we need this import? This commit seems to compile without it.

[snip]

> diff --git a/t/t0001-init.sh b/t/t0001-init.sh
> index f1d227f271..c7c464d3a2 100755
> --- a/t/t0001-init.sh
> +++ b/t/t0001-init.sh
> @@ -839,6 +839,36 @@ test_expect_success 'init with --ref-storage-format=garbage' '
>  	test_cmp expect err
>  '
>
> +test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage is not allowed with repo version 0' '
> +	test_when_finished "rm -rf objstorage" &&
> +	git init objstorage &&
> +	git -C objstorage config set extensions.objectStorage files &&
> +	test_must_fail git -C objstorage rev-parse 2>err &&
> +	test_grep "repo version is 0, but v1-only extension found" err
> +'
> +
> +test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with files backend' '
> +	test_when_finished "rm -rf objstorage" &&
> +	git init objstorage &&
> +	git -C objstorage config set core.repositoryformatversion 1 &&

If we're doing this, do we still need DEFAULT_REPO_FORMAT?

> +	git -C objstorage config set extensions.objectStorage files &&
> +	test_commit -C objstorage A &&
> +	git -C objstorage rev-parse --verify HEAD
> +'
> +
> +test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with unknown backend' '
> +	test_when_finished "rm -rf objstorage" &&
> +	git init objstorage &&
> +	git -C objstorage config set core.repositoryformatversion 1 &&

Same question here.

> +	git -C objstorage config set extensions.objectStorage garbage &&
> +	test_must_fail git -C objstorage rev-parse 2>err &&
> +	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage${SQ}" err &&
> +
> +	git config --file objstorage/.git/config extensions.objectStorage garbage://payload &&
> +	test_must_fail git -C objstorage rev-parse 2>err &&
> +	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage://payload${SQ}" err
> +'
> +

We should add  tests for the files ODB with {valid, invalid} payload. We
should probably also test absolute and relative paths in the valid flow.

>  test_expect_success MINGW 'core.hidedotfiles = false' '
>  	git config --global core.hidedotfiles false &&
>  	rm -rf newdir &&
>
> --
> 2.56.0.269.g68944adf51.dirty

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 3/7] setup: allow overriding object storage format via environment
  2026-10-19  8:55 ` [PATCH 3/7] setup: allow overriding object storage format via environment Patrick Steinhardt
@ 2026-10-19 21:34   ` Karthik Nayak
  2026-10-20  8:50     ` Patrick Steinhardt
  0 siblings, 1 reply; 49+ messages in thread
From: Karthik Nayak @ 2026-10-19 21:34 UTC (permalink / raw)
  To: Patrick Steinhardt, git

[-- Attachment #1: Type: text/plain, Size: 2026 bytes --]

Patrick Steinhardt <ps@pks.im> writes:

> The ref storage format configured via "extensions.refStorage" can be
> overridden with the GIT_REF_STORAGE_FORMAT environment variable.
> Introduce the GIT_OBJECT_STORAGE_FORMAT environment variable that
> overrides "extensions.objectStorage" in the same way.

[snip]

> diff --git a/t/t0001-init.sh b/t/t0001-init.sh
> index c7c464d3a2..5e7c25e42c 100755
> --- a/t/t0001-init.sh
> +++ b/t/t0001-init.sh
> @@ -869,6 +869,32 @@ test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with unknown b
>  	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage://payload${SQ}" err
>  '
>
> +test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FORMAT overrides extensions.objectStorage' '
> +	test_when_finished "rm -rf objstorage" &&
> +	git init objstorage &&
> +	(
> +		cd objstorage &&
> +		git config set core.repositoryformatversion 1 &&
> +		git config set extensions.objectStorage files://objs &&

This commit does test the payload for the files backend. I do wonder if
we can make a matrix for tests

   {config, env} x
   {valid backend, invalid backend} x
   {valid relative path, valid absolute path, invalid path}

> +		mkdir .git/objs .git/other &&
> +
> +		echo content >file &&
> +		oid=$(git hash-object -w file) &&
> +		test_path_is_file .git/objs/$(test_oid_to_path "$oid") &&
> +		git cat-file -e "$oid" &&
> +
> +		test_must_fail env GIT_OBJECT_STORAGE_FORMAT=files://other git cat-file -e "$oid" &&
> +		GIT_OBJECT_STORAGE_FORMAT=files://other git hash-object -w file &&
> +		test_path_is_file .git/other/$(test_oid_to_path "$oid") &&
> +
> +		cat >expect <<-EOF &&
> +		fatal: unknown object storage format: ${SQ}garbage${SQ}
> +		EOF
> +		test_must_fail env GIT_OBJECT_STORAGE_FORMAT=garbage git rev-parse 2>err &&
> +		test_cmp expect err
> +	)
> +'
> +
>  test_expect_success MINGW 'core.hidedotfiles = false' '
>  	git config --global core.hidedotfiles false &&
>  	rm -rf newdir &&
>
> --
> 2.56.0.269.g68944adf51.dirty

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 4/7] builtin: expose the object storage format
  2026-10-19  8:55 ` [PATCH 4/7] builtin: expose the object storage format Patrick Steinhardt
@ 2026-10-19 21:40   ` Karthik Nayak
  2026-10-20  8:50     ` Patrick Steinhardt
  0 siblings, 1 reply; 49+ messages in thread
From: Karthik Nayak @ 2026-10-19 21:40 UTC (permalink / raw)
  To: Patrick Steinhardt, git

[-- Attachment #1: Type: text/plain, Size: 2257 bytes --]

Patrick Steinhardt <ps@pks.im> writes:

> The ref storage format of a repository can be inspected via both
> git-rev-parse(1)'s "--show-ref-storage-format" and git-repo(1)'s
> "references.storageFormat" field. Introduce the equivalent options for
> the object storage format, as well.

[snip]

> diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
> index cb8c1e7292..d2dc9b7a69 100644
> --- a/Documentation/git-repo.adoc
> +++ b/Documentation/git-repo.adoc
> @@ -105,6 +105,12 @@ values that they return:
>  `object.format`::
>  	The object format (hash algorithm) used in the repository.
>
> +`objects.storageFormat`::
> +	The object storage format used in the repository, as configured via
> +	`extensions.objectStorage`. The valid values are:
> ++

The nuance between `object` above and `objects` here is too little and
can be confusing. But there's nothing we can really do here.

[snip]

> diff --git a/builtin/repo.c b/builtin/repo.c
> index 84e012f83f..da11a56ede 100644
> --- a/builtin/repo.c
> +++ b/builtin/repo.c
> @@ -128,11 +129,20 @@ static int get_references_format(struct repository *repo, struct strbuf *buf)
>  	return 0;
>  }
>
> +static int get_object_storage_format(struct repository *repo, struct strbuf *buf)
> +{
> +	strbuf_addstr(buf, repo->object_storage_format ?
> +		      repo->object_storage_format :
> +		      odb_source_type_to_name(ODB_SOURCE_FILES));
> +	return 0;
> +}

Is there a reason why `repo->object_storage_format` won't be set, I
don't like the fact that we have to expose the default object storage
format outside the repository and odb subsystem.

> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
> index ec33c19bdf..b206944ad9 100644
> @@ -1138,6 +1139,12 @@ int cmd_rev_parse(int argc,
>  				puts(ref_storage_format_to_name(the_repository->ref_storage_format));
>  				continue;
>  			}
> +			if (!strcmp(arg, "--show-object-storage-format")) {
> +				puts(the_repository->object_storage_format ?
> +				     the_repository->object_storage_format :
> +				     odb_source_type_to_name(ODB_SOURCE_FILES));

Same question here.

> +				continue;
> +			}
>  			if (!strcmp(arg, "--end-of-options")) {
>  				seen_end_of_options = 1;
>  				if (filter & (DO_FLAGS | DO_REVS))

[snip]

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 2/7] setup: introduce object storage extension
  2026-10-19 21:29   ` Karthik Nayak
@ 2026-10-20  1:27     ` Junio C Hamano
  2026-10-20 18:32       ` Karthik Nayak
  2026-10-20  8:50     ` Patrick Steinhardt
  1 sibling, 1 reply; 49+ messages in thread
From: Junio C Hamano @ 2026-10-20  1:27 UTC (permalink / raw)
  To: Karthik Nayak; +Cc: Patrick Steinhardt, git

Karthik Nayak <karthik.188@gmail.com> writes:

>> diff --git a/odb/source.c b/odb/source.c
>> index b25ef14df8..10abe33a26 100644
>> --- a/odb/source.c
>> +++ b/odb/source.c
>> @@ -1,4 +1,5 @@
>>  #include "git-compat-util.h"
>> +#include "gettext.h"
>>  #include "object-file.h"
>>  #include "odb/source-files.h"
>>  #include "odb/source.h"
>
> Do we need this import? This commit seems to compile without it.

It is better to be explicit, rather than relying on an indirect
inclusion from an unrelated header.  Since odb/source.c is more
or less a library-like part of the system, however, the presence
of translatable messages meant for human users is a bit of a
curiosity.

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 1/7] loose: drop unused `repo_write_loose_object_map()`
  2026-10-19 20:21   ` Karthik Nayak
@ 2026-10-20  8:49     ` Patrick Steinhardt
  2026-10-20 18:30       ` Karthik Nayak
  0 siblings, 1 reply; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-20  8:49 UTC (permalink / raw)
  To: Karthik Nayak; +Cc: git

On Mon, Oct 19, 2026 at 01:21:08PM -0700, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > The function `repo_write_loose_object_map()` was introduced in
> > 23b2c7e95b (loose: add a mapping between SHA-1 and SHA-256 for loose
> > objects, 2023-10-01), but it never had any callers as loose object maps
> > are instead written incrementally via `repo_add_loose_object_map()`
> > whenever a new loose object is created.
> >
> > Besides being dead code, the function also hardcodes the location of the
> > "loose-object-idx" file as "$GIT_COMMON_DIR/objects", whereas both the
> > reader and `repo_add_loose_object_map()` derive the location from the
> > loose source's path. The function would thus disagree with everyone else
> > where the loose object map is supposed to be located when the object
> > directory is using a non-default location.
> >
> > Remove the function.
> >
> 
> It's like the function being unused is an insufficient argument by
> itself, so we also talk about why its incorrect. Made me chuckle.

Yeah. The only reason that I even stumbled over this function is that
it'll break with the object storage format extension, because it would
put files into the wrong location. I only realized later on that the
function isn't even used at all. But that probably explains why this
message is a bit on the excessive side.

Should I trim it?

Patrick

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 2/7] setup: introduce object storage extension
  2026-10-19 21:29   ` Karthik Nayak
  2026-10-20  1:27     ` Junio C Hamano
@ 2026-10-20  8:50     ` Patrick Steinhardt
  2026-10-20 19:06       ` Karthik Nayak
  1 sibling, 1 reply; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-20  8:50 UTC (permalink / raw)
  To: Karthik Nayak; +Cc: git

On Mon, Oct 19, 2026 at 02:29:20PM -0700, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/odb/source-files.c b/odb/source-files.c
> > index 455f409db0..4a7cbbfd9b 100644
> > --- a/odb/source-files.c
> > +++ b/odb/source-files.c
> > @@ -1245,6 +1246,12 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
> >  		object_dir = xstrdup_or_null(getenv(DB_ENVIRONMENT));
> >  		alternates = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));
> >  	}
> > +	if (!object_dir && payload) {
> > +		if (is_absolute_path(payload))
> > +			object_dir = xstrdup(payload);
> > +		else
> > +			object_dir = xstrfmt("%s/%s", odb->repo->commondir, payload);
> > +	}
> 
> On the reference side, we use `strbuf_realpath()` to convert the
> relative paths to absolute paths, should we be doing that here too?

Overall I'd love for the "files" source to only ever use absolute paths,
but historically that wasn't the case. We sometimes expose those paths
to users, and changing the "files" source thus breaks a bunch of tests.

So I'm retaining that behaviour for now, also with the extension. It may
be something to explore going forward though.

> > diff --git a/odb/source.c b/odb/source.c
> > index b25ef14df8..10abe33a26 100644
> > --- a/odb/source.c
> > +++ b/odb/source.c
> > @@ -1,4 +1,5 @@
> >  #include "git-compat-util.h"
> > +#include "gettext.h"
> >  #include "object-file.h"
> >  #include "odb/source-files.h"
> >  #include "odb/source.h"
> 
> Do we need this import? This commit seems to compile without it.

Yeah, we add some translatable strings to `odb_source_new()`. The header
is also implicitly included, but as Junio pointed out we should include
headers explicitly regardless when we require their declarations.

> > diff --git a/t/t0001-init.sh b/t/t0001-init.sh
> > index f1d227f271..c7c464d3a2 100755
> > --- a/t/t0001-init.sh
> > +++ b/t/t0001-init.sh
> > @@ -839,6 +839,36 @@ test_expect_success 'init with --ref-storage-format=garbage' '
> >  	test_cmp expect err
> >  '
> >
> > +test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage is not allowed with repo version 0' '
> > +	test_when_finished "rm -rf objstorage" &&
> > +	git init objstorage &&
> > +	git -C objstorage config set extensions.objectStorage files &&
> > +	test_must_fail git -C objstorage rev-parse 2>err &&
> > +	test_grep "repo version is 0, but v1-only extension found" err
> > +'
> > +
> > +test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with files backend' '
> > +	test_when_finished "rm -rf objstorage" &&
> > +	git init objstorage &&
> > +	git -C objstorage config set core.repositoryformatversion 1 &&
> 
> If we're doing this, do we still need DEFAULT_REPO_FORMAT?

We could indeed get away with not having the prerequisite right now. But
once we have a second backend we'd need this, because in that case we'd
potentially initialize the repository with a different backend here. And
then just switching over to the "files" backend would probably not
result in anything good.

So this is more of a protective measure for the future.

> > +	git -C objstorage config set extensions.objectStorage garbage &&
> > +	test_must_fail git -C objstorage rev-parse 2>err &&
> > +	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage${SQ}" err &&
> > +
> > +	git config --file objstorage/.git/config extensions.objectStorage garbage://payload &&
> > +	test_must_fail git -C objstorage rev-parse 2>err &&
> > +	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage://payload${SQ}" err
> > +'
> > +
> 
> We should add  tests for the files ODB with {valid, invalid} payload. We
> should probably also test absolute and relative paths in the valid flow.

I add tests for both absolute and relative paths in a later commit. Here
I'm basically just verifying whether the object storage extension even
kicks in at all, without yet verifying its semantics properly.

Patrick

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 4/7] builtin: expose the object storage format
  2026-10-19 21:40   ` Karthik Nayak
@ 2026-10-20  8:50     ` Patrick Steinhardt
  2026-10-20 19:10       ` Karthik Nayak
  0 siblings, 1 reply; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-20  8:50 UTC (permalink / raw)
  To: Karthik Nayak; +Cc: git

On Mon, Oct 19, 2026 at 02:40:31PM -0700, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
> > index cb8c1e7292..d2dc9b7a69 100644
> > --- a/Documentation/git-repo.adoc
> > +++ b/Documentation/git-repo.adoc
> > @@ -105,6 +105,12 @@ values that they return:
> >  `object.format`::
> >  	The object format (hash algorithm) used in the repository.
> >
> > +`objects.storageFormat`::
> > +	The object storage format used in the repository, as configured via
> > +	`extensions.objectStorage`. The valid values are:
> > ++
> 
> The nuance between `object` above and `objects` here is too little and
> can be confusing. But there's nothing we can really do here.

Yeah, I'm not a fan of this, either. It used to be even worse than that,
because we had "references.format" which was giving us information about
the backend that our refs use. And that's why I recently sent the patch
series to rename "ref format" to "ref storage format", so that we can
avoid this conflict here.

It's still not perfect to have both "object.format" and
"objects.storageFormat". I was wondering whether we also want to rename
"object.format" to "objects.format" to at least have the prefix match.
But I shied away from that.

> > diff --git a/builtin/repo.c b/builtin/repo.c
> > index 84e012f83f..da11a56ede 100644
> > --- a/builtin/repo.c
> > +++ b/builtin/repo.c
> > @@ -128,11 +129,20 @@ static int get_references_format(struct repository *repo, struct strbuf *buf)
> >  	return 0;
> >  }
> >
> > +static int get_object_storage_format(struct repository *repo, struct strbuf *buf)
> > +{
> > +	strbuf_addstr(buf, repo->object_storage_format ?
> > +		      repo->object_storage_format :
> > +		      odb_source_type_to_name(ODB_SOURCE_FILES));
> > +	return 0;
> > +}
> 
> Is there a reason why `repo->object_storage_format` won't be set, I
> don't like the fact that we have to expose the default object storage
> format outside the repository and odb subsystem.

Hmm, not really, and I agree that this creates some awkwardness. I'll
have a look at whether I can fix that.

Patrick

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 3/7] setup: allow overriding object storage format via environment
  2026-10-19 21:34   ` Karthik Nayak
@ 2026-10-20  8:50     ` Patrick Steinhardt
  2026-10-20 19:08       ` Karthik Nayak
  0 siblings, 1 reply; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-20  8:50 UTC (permalink / raw)
  To: Karthik Nayak; +Cc: git

On Mon, Oct 19, 2026 at 05:34:39PM -0400, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/t/t0001-init.sh b/t/t0001-init.sh
> > index c7c464d3a2..5e7c25e42c 100755
> > --- a/t/t0001-init.sh
> > +++ b/t/t0001-init.sh
> > @@ -869,6 +869,32 @@ test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with unknown b
> >  	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage://payload${SQ}" err
> >  '
> >
> > +test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FORMAT overrides extensions.objectStorage' '
> > +	test_when_finished "rm -rf objstorage" &&
> > +	git init objstorage &&
> > +	(
> > +		cd objstorage &&
> > +		git config set core.repositoryformatversion 1 &&
> > +		git config set extensions.objectStorage files://objs &&
> 
> This commit does test the payload for the files backend. I do wonder if
> we can make a matrix for tests
> 
>    {config, env} x
>    {valid backend, invalid backend} x
>    {valid relative path, valid absolute path, invalid path}

There's more to this test matrix: GIT_DEFAULT_OBJECT_STORAGE_FORMAT,
"init.defaultObjectStorageFormat", "git init --object-storage-format=".
So that would result in a matrix that covers 30 different combinations,
which I feel might be a bit excessive.

That being said I do add lots of tests especially in the patch that
extends git-init(1), even though they're not layed out in a matrix. I'd
propose that we go from there and extend these tests in case there's
anything missing there?

I think the one thing that's missing is exercising invalid paths. The
question though is what an invalid path even is. Right now for example
we use `safe_create_dir()`, so we won't create leading directories. I'm
not sure whether that restriction is a sensible one, and because of this
I shy away from seemingly casting it into stone via a test. But if we
eventually start to create leading directories, then there isn't really
any path that I would consider invalid, right?

Patrick

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 1/7] loose: drop unused `repo_write_loose_object_map()`
  2026-10-20  8:49     ` Patrick Steinhardt
@ 2026-10-20 18:30       ` Karthik Nayak
  0 siblings, 0 replies; 49+ messages in thread
From: Karthik Nayak @ 2026-10-20 18:30 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 1509 bytes --]

Patrick Steinhardt <ps@pks.im> writes:

> On Mon, Oct 19, 2026 at 01:21:08PM -0700, Karthik Nayak wrote:
>> Patrick Steinhardt <ps@pks.im> writes:
>>
>> > The function `repo_write_loose_object_map()` was introduced in
>> > 23b2c7e95b (loose: add a mapping between SHA-1 and SHA-256 for loose
>> > objects, 2023-10-01), but it never had any callers as loose object maps
>> > are instead written incrementally via `repo_add_loose_object_map()`
>> > whenever a new loose object is created.
>> >
>> > Besides being dead code, the function also hardcodes the location of the
>> > "loose-object-idx" file as "$GIT_COMMON_DIR/objects", whereas both the
>> > reader and `repo_add_loose_object_map()` derive the location from the
>> > loose source's path. The function would thus disagree with everyone else
>> > where the loose object map is supposed to be located when the object
>> > directory is using a non-default location.
>> >
>> > Remove the function.
>> >
>>
>> It's like the function being unused is an insufficient argument by
>> itself, so we also talk about why its incorrect. Made me chuckle.
>
> Yeah. The only reason that I even stumbled over this function is that
> it'll break with the object storage format extension, because it would
> put files into the wrong location. I only realized later on that the
> function isn't even used at all. But that probably explains why this
> message is a bit on the excessive side.
>
> Should I trim it?
>

I think its good, I just was amused by it.

> Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 2/7] setup: introduce object storage extension
  2026-10-20  1:27     ` Junio C Hamano
@ 2026-10-20 18:32       ` Karthik Nayak
  0 siblings, 0 replies; 49+ messages in thread
From: Karthik Nayak @ 2026-10-20 18:32 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Patrick Steinhardt, git

[-- Attachment #1: Type: text/plain, Size: 819 bytes --]

Junio C Hamano <gitster@pobox.com> writes:

> Karthik Nayak <karthik.188@gmail.com> writes:
>
>>> diff --git a/odb/source.c b/odb/source.c
>>> index b25ef14df8..10abe33a26 100644
>>> --- a/odb/source.c
>>> +++ b/odb/source.c
>>> @@ -1,4 +1,5 @@
>>>  #include "git-compat-util.h"
>>> +#include "gettext.h"
>>>  #include "object-file.h"
>>>  #include "odb/source-files.h"
>>>  #include "odb/source.h"
>>
>> Do we need this import? This commit seems to compile without it.
>
> It is better to be explicit, rather than relying on an indirect
> inclusion from an unrelated header.  Since odb/source.c is more
> or less a library-like part of the system, however, the presence
> of translatable messages meant for human users is a bit of a
> curiosity.

Ah! it's for the '_()' translation, I missed that. Makes sense. Thanks

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 2/7] setup: introduce object storage extension
  2026-10-20  8:50     ` Patrick Steinhardt
@ 2026-10-20 19:06       ` Karthik Nayak
  0 siblings, 0 replies; 49+ messages in thread
From: Karthik Nayak @ 2026-10-20 19:06 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 4282 bytes --]

Patrick Steinhardt <ps@pks.im> writes:

> On Mon, Oct 19, 2026 at 02:29:20PM -0700, Karthik Nayak wrote:
>> Patrick Steinhardt <ps@pks.im> writes:
>> > diff --git a/odb/source-files.c b/odb/source-files.c
>> > index 455f409db0..4a7cbbfd9b 100644
>> > --- a/odb/source-files.c
>> > +++ b/odb/source-files.c
>> > @@ -1245,6 +1246,12 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
>> >  		object_dir = xstrdup_or_null(getenv(DB_ENVIRONMENT));
>> >  		alternates = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));
>> >  	}
>> > +	if (!object_dir && payload) {
>> > +		if (is_absolute_path(payload))
>> > +			object_dir = xstrdup(payload);
>> > +		else
>> > +			object_dir = xstrfmt("%s/%s", odb->repo->commondir, payload);
>> > +	}
>>
>> On the reference side, we use `strbuf_realpath()` to convert the
>> relative paths to absolute paths, should we be doing that here too?
>
> Overall I'd love for the "files" source to only ever use absolute paths,
> but historically that wasn't the case. We sometimes expose those paths
> to users, and changing the "files" source thus breaks a bunch of tests.
>
> So I'm retaining that behaviour for now, also with the extension. It may
> be something to explore going forward though.
>
>> > diff --git a/odb/source.c b/odb/source.c
>> > index b25ef14df8..10abe33a26 100644
>> > --- a/odb/source.c
>> > +++ b/odb/source.c
>> > @@ -1,4 +1,5 @@
>> >  #include "git-compat-util.h"
>> > +#include "gettext.h"
>> >  #include "object-file.h"
>> >  #include "odb/source-files.h"
>> >  #include "odb/source.h"
>>
>> Do we need this import? This commit seems to compile without it.
>
> Yeah, we add some translatable strings to `odb_source_new()`. The header
> is also implicitly included, but as Junio pointed out we should include
> headers explicitly regardless when we require their declarations.
>
>> > diff --git a/t/t0001-init.sh b/t/t0001-init.sh
>> > index f1d227f271..c7c464d3a2 100755
>> > --- a/t/t0001-init.sh
>> > +++ b/t/t0001-init.sh
>> > @@ -839,6 +839,36 @@ test_expect_success 'init with --ref-storage-format=garbage' '
>> >  	test_cmp expect err
>> >  '
>> >
>> > +test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage is not allowed with repo version 0' '
>> > +	test_when_finished "rm -rf objstorage" &&
>> > +	git init objstorage &&
>> > +	git -C objstorage config set extensions.objectStorage files &&
>> > +	test_must_fail git -C objstorage rev-parse 2>err &&
>> > +	test_grep "repo version is 0, but v1-only extension found" err
>> > +'
>> > +
>> > +test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with files backend' '
>> > +	test_when_finished "rm -rf objstorage" &&
>> > +	git init objstorage &&
>> > +	git -C objstorage config set core.repositoryformatversion 1 &&
>>
>> If we're doing this, do we still need DEFAULT_REPO_FORMAT?
>
> We could indeed get away with not having the prerequisite right now. But
> once we have a second backend we'd need this, because in that case we'd
> potentially initialize the repository with a different backend here. And
> then just switching over to the "files" backend would probably not
> result in anything good.
>
> So this is more of a protective measure for the future.
>

Yeah but this also means that this doesn't run with refs reftable, no?
It should be okay, I'll leave it to you.

>> > +	git -C objstorage config set extensions.objectStorage garbage &&
>> > +	test_must_fail git -C objstorage rev-parse 2>err &&
>> > +	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage${SQ}" err &&
>> > +
>> > +	git config --file objstorage/.git/config extensions.objectStorage garbage://payload &&
>> > +	test_must_fail git -C objstorage rev-parse 2>err &&
>> > +	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage://payload${SQ}" err
>> > +'
>> > +
>>
>> We should add  tests for the files ODB with {valid, invalid} payload. We
>> should probably also test absolute and relative paths in the valid flow.
>
> I add tests for both absolute and relative paths in a later commit. Here
> I'm basically just verifying whether the object storage extension even
> kicks in at all, without yet verifying its semantics properly.
>
> Patrick

Yeah, I think I saw those after this email.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 3/7] setup: allow overriding object storage format via environment
  2026-10-20  8:50     ` Patrick Steinhardt
@ 2026-10-20 19:08       ` Karthik Nayak
  0 siblings, 0 replies; 49+ messages in thread
From: Karthik Nayak @ 2026-10-20 19:08 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 2246 bytes --]

Patrick Steinhardt <ps@pks.im> writes:

> On Mon, Oct 19, 2026 at 05:34:39PM -0400, Karthik Nayak wrote:
>> Patrick Steinhardt <ps@pks.im> writes:
>> > diff --git a/t/t0001-init.sh b/t/t0001-init.sh
>> > index c7c464d3a2..5e7c25e42c 100755
>> > --- a/t/t0001-init.sh
>> > +++ b/t/t0001-init.sh
>> > @@ -869,6 +869,32 @@ test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with unknown b
>> >  	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage://payload${SQ}" err
>> >  '
>> >
>> > +test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FORMAT overrides extensions.objectStorage' '
>> > +	test_when_finished "rm -rf objstorage" &&
>> > +	git init objstorage &&
>> > +	(
>> > +		cd objstorage &&
>> > +		git config set core.repositoryformatversion 1 &&
>> > +		git config set extensions.objectStorage files://objs &&
>>
>> This commit does test the payload for the files backend. I do wonder if
>> we can make a matrix for tests
>>
>>    {config, env} x
>>    {valid backend, invalid backend} x
>>    {valid relative path, valid absolute path, invalid path}
>
> There's more to this test matrix: GIT_DEFAULT_OBJECT_STORAGE_FORMAT,
> "init.defaultObjectStorageFormat", "git init --object-storage-format=".
> So that would result in a matrix that covers 30 different combinations,
> which I feel might be a bit excessive.
>
> That being said I do add lots of tests especially in the patch that
> extends git-init(1), even though they're not layed out in a matrix. I'd
> propose that we go from there and extend these tests in case there's
> anything missing there?
>

That's okay, I only was curious if we'd miss anything. The matrix is
easier to think about.

> I think the one thing that's missing is exercising invalid paths. The
> question though is what an invalid path even is. Right now for example
> we use `safe_create_dir()`, so we won't create leading directories. I'm
> not sure whether that restriction is a sensible one, and because of this
> I shy away from seemingly casting it into stone via a test. But if we
> eventually start to create leading directories, then there isn't really
> any path that I would consider invalid, right?
>
> Patrick

Yeah that's fair. It's all good then.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH 4/7] builtin: expose the object storage format
  2026-10-20  8:50     ` Patrick Steinhardt
@ 2026-10-20 19:10       ` Karthik Nayak
  0 siblings, 0 replies; 49+ messages in thread
From: Karthik Nayak @ 2026-10-20 19:10 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 1481 bytes --]

Patrick Steinhardt <ps@pks.im> writes:

> On Mon, Oct 19, 2026 at 02:40:31PM -0700, Karthik Nayak wrote:
>> Patrick Steinhardt <ps@pks.im> writes:
>> > diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
>> > index cb8c1e7292..d2dc9b7a69 100644
>> > --- a/Documentation/git-repo.adoc
>> > +++ b/Documentation/git-repo.adoc
>> > @@ -105,6 +105,12 @@ values that they return:
>> >  `object.format`::
>> >  	The object format (hash algorithm) used in the repository.
>> >
>> > +`objects.storageFormat`::
>> > +	The object storage format used in the repository, as configured via
>> > +	`extensions.objectStorage`. The valid values are:
>> > ++
>>
>> The nuance between `object` above and `objects` here is too little and
>> can be confusing. But there's nothing we can really do here.
>
> Yeah, I'm not a fan of this, either. It used to be even worse than that,
> because we had "references.format" which was giving us information about
> the backend that our refs use. And that's why I recently sent the patch
> series to rename "ref format" to "ref storage format", so that we can
> avoid this conflict here.
>
> It's still not perfect to have both "object.format" and
> "objects.storageFormat". I was wondering whether we also want to rename
> "object.format" to "objects.format" to at least have the prefix match.
> But I shied away from that.
>

But then you'd have to also rename the config options for the same no?
I'd leave that out of this series.

[snip]

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH v2 0/7] odb: introduce object storage extension
  2026-10-19  8:55 [PATCH 0/7] odb: introduce object storage extension Patrick Steinhardt
                   ` (6 preceding siblings ...)
  2026-10-19  8:56 ` [PATCH 7/7] builtin/submodule: propagate "--object-storage-format=" Patrick Steinhardt
@ 2026-10-21  5:39 ` Patrick Steinhardt
  2026-10-21  5:39   ` [PATCH v2 1/7] loose: drop unused `repo_write_loose_object_map()` Patrick Steinhardt
                     ` (7 more replies)
  2026-10-23 11:49 ` [PATCH v3 " Patrick Steinhardt
  8 siblings, 8 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-21  5:39 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

Hi,

this patch series introduces the object storage extension. The extension
is of course similar in spirit and design to the ref storage extension:
it can be configured via "extensions.objectStorage", has the same syntax
with "<schema>[://<payload>]", and we have the same mechanisms to
configure it ("--object-storage-format=", GIT_OBJECT_STORAGE_FORMAT,
GIT_DEFAULT_OBJECT_STORAGE_FORMAT, "init.defaultObjetcStorageFormat").

For now, we only support the "files" object storage format, so not a lot
of stuff really changes. That being said, its optional payload allows
you to configure a different location for the ".git/objects" directory.
So if you specify "extensions.objectStorage=files://objs", objects would
be stored in ".git/objs" instead.

The series is built on top of c51408889c (The 6th batch, 2026-10-16)
with the following dependencies merged into it:

  - ps/odb-files-alternates at 0ba59c8453 (odb/source: drop
    `read_alternates` callback, 2026-10-08) merged into it.

  - ps/setup-enforce-repo-passed-to-create-repository-has-no-state
    (phew, what a mouthful) at 0ba59c8453 (odb/source: drop
    `read_alternates` callback, 2026-10-08).

Note that these two series conflict with one another. The conflict can
easily be resolved by just deleting the whole conflicting sections.

Changes in v2:
  - Ensure that `repo->object_storage_format` is always populated.
  - Link to v1: https://patch.msgid.link/20261019-b4-pks-odb-extension-v1-0-143a5963b77f@pks.im

Thanks!

Patrick

---
Patrick Steinhardt (7):
      loose: drop unused `repo_write_loose_object_map()`
      setup: introduce object storage extension
      setup: allow overriding object storage format via environment
      builtin: expose the object storage format
      builtin/init: introduce handling of object storage format
      builtin/clone: introduce "--object-storage-format="
      builtin/submodule: propagate "--object-storage-format="

 Documentation/config/extensions.adoc       |  23 +++
 Documentation/config/init.adoc             |   5 +
 Documentation/git-clone.adoc               |  11 ++
 Documentation/git-init.adoc                |  10 +-
 Documentation/git-repo.adoc                |   6 +
 Documentation/git-rev-parse.adoc           |   5 +
 Documentation/git-submodule.adoc           |  10 +-
 Documentation/git.adoc                     |  10 ++
 Documentation/object-storage-format.adoc   |   6 +
 builtin/clone.c                            |  58 +++++++-
 builtin/init-db.c                          |   7 +-
 builtin/repo.c                             |   8 ++
 builtin/rev-parse.c                        |   5 +
 builtin/submodule--helper.c                |  28 ++++
 environment.h                              |   1 +
 git-submodule.sh                           |  18 +++
 loose.c                                    |  46 ------
 loose.h                                    |   1 -
 odb.c                                      |   2 +-
 odb/source-files.c                         |   7 +
 odb/source-files.h                         |   1 +
 odb/source.c                               |  45 +++++-
 odb/source.h                               |  12 +-
 repository.c                               |  11 ++
 repository.h                               |   8 ++
 setup.c                                    | 116 ++++++++++++++-
 setup.h                                    |   2 +
 t/meson.build                              |   1 +
 t/t0001-init.sh                            | 221 +++++++++++++++++++++++++++++
 t/t1900-repo-info.sh                       |   3 +
 t/t5601-clone.sh                           |  68 +++++++++
 t/t7427-submodule-object-storage-format.sh |  91 ++++++++++++
 t/unit-tests/u-odb-inmemory.c              |   1 +
 33 files changed, 785 insertions(+), 62 deletions(-)

Range-diff versus v1:

1:  c9d303a0a4 = 1:  49f964eeec loose: drop unused `repo_write_loose_object_map()`
2:  1090634b33 ! 2:  82849d866a setup: introduce object storage extension
    @@ odb/source.c: const char *odb_source_type_to_name(enum odb_source_type type)
     +	struct odb_source *source;
     +	char *payload = NULL;
     +
    -+	if (!object_storage_format)
    -+		return &odb_source_files_new(odb, NULL, flags)->base;
    -+
     +	switch (odb_source_type_by_uri(object_storage_format, &payload)) {
     +	case ODB_SOURCE_FILES:
     +		source = &odb_source_files_new(odb, payload, flags)->base;
    @@ repository.c: void repo_set_ref_storage_format(struct repository *repo,
     +				    const char *uri)
     +{
     +	free(repo->object_storage_format);
    -+	repo->object_storage_format = xstrdup_or_null(uri);
    ++	if (uri)
    ++		repo->object_storage_format = xstrdup(uri);
    ++	else
    ++		repo->object_storage_format = xstrdup("files");
     +}
     +
      /*
    @@ t/t0001-init.sh: test_expect_success 'init with --ref-storage-format=garbage' '
      test_expect_success MINGW 'core.hidedotfiles = false' '
      	git config --global core.hidedotfiles false &&
      	rm -rf newdir &&
    +
    + ## t/unit-tests/u-odb-inmemory.c ##
    +@@
    + 
    + static struct repository repo = {
    + 	.hash_algo = &hash_algos[GIT_HASH_SHA1],
    ++	.object_storage_format = (char *) "files",
    + };
    + static struct object_database *odb;
    + 
3:  afbd06d834 = 3:  5d5728f5a3 setup: allow overriding object storage format via environment
4:  aba2dd3339 ! 4:  9b408ae68e builtin: expose the object storage format
    @@ builtin/repo.c: static int get_references_format(struct repository *repo, struct
      
     +static int get_object_storage_format(struct repository *repo, struct strbuf *buf)
     +{
    -+	strbuf_addstr(buf, repo->object_storage_format ?
    -+		      repo->object_storage_format :
    -+		      odb_source_type_to_name(ODB_SOURCE_FILES));
    ++	strbuf_addstr(buf, repo->object_storage_format);
     +	return 0;
     +}
     +
    @@ builtin/rev-parse.c: int cmd_rev_parse(int argc,
      				continue;
      			}
     +			if (!strcmp(arg, "--show-object-storage-format")) {
    -+				puts(the_repository->object_storage_format ?
    -+				     the_repository->object_storage_format :
    -+				     odb_source_type_to_name(ODB_SOURCE_FILES));
    ++				puts(the_repository->object_storage_format);
     +				continue;
     +			}
      			if (!strcmp(arg, "--end-of-options")) {
5:  a61a7f674d ! 5:  ca8be36980 builtin/init: introduce handling of object storage format
    @@ setup.c: void initialize_repository_version(struct repository *repo,
      	    ref_storage_format != REF_STORAGE_FORMAT_FILES ||
     -	    repo->ref_storage_payload)
     +	    repo->ref_storage_payload ||
    -+	    repo->object_storage_format)
    ++	    strcmp(repo->object_storage_format, "files"))
      		target_version = GIT_REPO_VERSION_READ;
      
      	if (hash_algo != GIT_HASH_SHA1_LEGACY && hash_algo != GIT_HASH_UNKNOWN)
    @@ setup.c: void initialize_repository_version(struct repository *repo,
      		repo_config_set_gently(repo, "extensions.refstorage", NULL);
      	}
      
    -+	if (repo->object_storage_format)
    ++	if (strcmp(repo->object_storage_format, "files"))
     +		repo_config_set(repo, "extensions.objectstorage",
     +				repo->object_storage_format);
     +	else if (reinit)
6:  a61c708bc9 = 6:  34c9345892 builtin/clone: introduce "--object-storage-format="
7:  1d2c6a3cd0 = 7:  95c9be6418 builtin/submodule: propagate "--object-storage-format="

---
base-commit: 0d31382da9336683214372ba328fe9ecbc8bcb9a
change-id: 20261006-b4-pks-odb-extension-36de693bb943


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH v2 1/7] loose: drop unused `repo_write_loose_object_map()`
  2026-10-21  5:39 ` [PATCH v2 0/7] odb: introduce object storage extension Patrick Steinhardt
@ 2026-10-21  5:39   ` Patrick Steinhardt
  2026-10-21  5:39   ` [PATCH v2 2/7] setup: introduce object storage extension Patrick Steinhardt
                     ` (6 subsequent siblings)
  7 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-21  5:39 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

The function `repo_write_loose_object_map()` was introduced in
23b2c7e95b (loose: add a mapping between SHA-1 and SHA-256 for loose
objects, 2023-10-01), but it never had any callers as loose object maps
are instead written incrementally via `repo_add_loose_object_map()`
whenever a new loose object is created.

Besides being dead code, the function also hardcodes the location of the
"loose-object-idx" file as "$GIT_COMMON_DIR/objects", whereas both the
reader and `repo_add_loose_object_map()` derive the location from the
loose source's path. The function would thus disagree with everyone else
where the loose object map is supposed to be located when the object
directory is using a non-default location.

Remove the function.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 loose.c | 46 ----------------------------------------------
 loose.h |  1 -
 2 files changed, 47 deletions(-)

diff --git a/loose.c b/loose.c
index d6a2bb5ba3..7e26a04ec0 100644
--- a/loose.c
+++ b/loose.c
@@ -123,52 +123,6 @@ int repo_read_loose_object_map(struct repository *repo)
 	return 0;
 }
 
-int repo_write_loose_object_map(struct repository *repo)
-{
-	struct odb_source_files *files = odb_source_files_downcast(repo->objects->source);
-	kh_oid_map_t *map = files->dirs->loose->map->to_compat;
-	struct lock_file lock;
-	int fd;
-	khiter_t iter;
-	struct strbuf buf = STRBUF_INIT, path = STRBUF_INIT;
-
-	if (!should_use_loose_object_map(repo))
-		return 0;
-
-	repo_common_path_replace(repo, &path, "objects/loose-object-idx");
-	fd = repo_hold_lock_file_for_update_timeout(repo, &lock, path.buf,
-						    LOCK_DIE_ON_ERROR, -1);
-	iter = kh_begin(map);
-	if (write_in_full(fd, loose_object_header, strlen(loose_object_header)) < 0)
-		goto errout;
-
-	for (; iter != kh_end(map); iter++) {
-		if (kh_exist(map, iter)) {
-			if (oideq(&kh_key(map, iter), repo->hash_algo->empty_tree) ||
-			    oideq(&kh_key(map, iter), repo->hash_algo->empty_blob))
-				continue;
-			strbuf_addf(&buf, "%s %s\n", oid_to_hex(&kh_key(map, iter)), oid_to_hex(kh_value(map, iter)));
-			if (write_in_full(fd, buf.buf, buf.len) < 0)
-				goto errout;
-			strbuf_reset(&buf);
-		}
-	}
-	strbuf_release(&buf);
-	if (commit_lock_file(&lock) < 0) {
-		error_errno(_("could not write loose object index %s"), path.buf);
-		strbuf_release(&path);
-		return -1;
-	}
-	strbuf_release(&path);
-	return 0;
-errout:
-	rollback_lock_file(&lock);
-	strbuf_release(&buf);
-	error_errno(_("failed to write loose object index %s"), path.buf);
-	strbuf_release(&path);
-	return -1;
-}
-
 static int write_one_object(struct odb_source_loose *loose,
 			    const struct object_id *oid,
 			    const struct object_id *compat_oid)
diff --git a/loose.h b/loose.h
index ed663ac550..41c4b46818 100644
--- a/loose.h
+++ b/loose.h
@@ -22,6 +22,5 @@ int repo_add_loose_object_map(struct odb_source_loose *loose,
 			      const struct object_id *oid,
 			      const struct object_id *compat_oid);
 int repo_read_loose_object_map(struct repository *repo);
-int repo_write_loose_object_map(struct repository *repo);
 
 #endif

-- 
2.56.0.307.g5fe7bd680c.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v2 2/7] setup: introduce object storage extension
  2026-10-21  5:39 ` [PATCH v2 0/7] odb: introduce object storage extension Patrick Steinhardt
  2026-10-21  5:39   ` [PATCH v2 1/7] loose: drop unused `repo_write_loose_object_map()` Patrick Steinhardt
@ 2026-10-21  5:39   ` Patrick Steinhardt
  2026-10-21  5:39   ` [PATCH v2 3/7] setup: allow overriding object storage format via environment Patrick Steinhardt
                     ` (5 subsequent siblings)
  7 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-21  5:39 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

Introduce a new "extensions.objectStorage" extension that allows the
user to configure the primary object database source of a repository.
The value is a URI in the format "<schema>[://<payload>]", mirroring
the format used by "extensions.refStorage":

  - The schema identifies the backend that shall be used. For now the
    only supported schema is "files", which is the existing object
    database backed by loose objects and packfiles.

  - The payload can be passed to configure the backend, and its
    interpretation is backend-specific. For the "files" backend, it
    allows you to configure a different location for the ".git/objects"
    directory.

Eventually, this mechanism will be used to support alternative backends.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/config/extensions.adoc     | 23 ++++++++++++++++
 Documentation/object-storage-format.adoc |  6 +++++
 odb.c                                    |  2 +-
 odb/source-files.c                       |  7 +++++
 odb/source-files.h                       |  1 +
 odb/source.c                             | 45 +++++++++++++++++++++++++++++++-
 odb/source.h                             | 12 ++++++++-
 repository.c                             | 11 ++++++++
 repository.h                             |  8 ++++++
 setup.c                                  | 13 +++++++++
 setup.h                                  |  1 +
 t/t0001-init.sh                          | 30 +++++++++++++++++++++
 t/unit-tests/u-odb-inmemory.c            |  1 +
 13 files changed, 157 insertions(+), 3 deletions(-)

diff --git a/Documentation/config/extensions.adoc b/Documentation/config/extensions.adoc
index be6678bb5b..4f3a66a454 100644
--- a/Documentation/config/extensions.adoc
+++ b/Documentation/config/extensions.adoc
@@ -56,6 +56,29 @@ preciousObjects:::
 For historical reasons, this extension is respected regardless of the
 `core.repositoryFormatVersion` setting.
 
+objectStorage:::
+	Specify the object storage format and a corresponding payload. The
+	value can be either a format name or a URI:
++
+--
+* A format name alone (e.g., `files`).
+
+* A URI format `<format>://<payload>` explicitly specifies both the
+  format and payload (e.g., `files:///foo/bar`).
+
+Supported formats are:
+
+include::../object-storage-format.adoc[]
+
+--
++
+Note that this setting should only be set by linkgit:git-init[1] or
+linkgit:git-clone[1]. Trying to change it after initialization will make
+preexisting objects inaccessible.
++
+NOTE: This extension is still considered EXPERIMENTAL and may be subject to
+change.
+
 refStorage:::
 	Specify the ref storage format and a corresponding payload. The value
 	can be either a format name or a URI:
diff --git a/Documentation/object-storage-format.adoc b/Documentation/object-storage-format.adoc
new file mode 100644
index 0000000000..4c4aef6dda
--- /dev/null
+++ b/Documentation/object-storage-format.adoc
@@ -0,0 +1,6 @@
+`files[://<path>]`;; for loose objects and packfiles. This is the default.
++
+The optional payload can be passed to specify an alternative filesystem path
+where the objects will be stored. Relative paths are resolved relative to the
+`$GIT_COMMON_DIR`. Defaults to `$GIT_COMMON_DIR/objects` when no payload is
+provided. The payload is ignored when `GIT_OBJECT_DIRECTORY` is set.
diff --git a/odb.c b/odb.c
index 98c5c862f5..6c07b3b671 100644
--- a/odb.c
+++ b/odb.c
@@ -757,7 +757,7 @@ struct object_database *odb_new(struct repository *repo,
 	o->repo = repo;
 	pthread_mutex_init(&o->replace_mutex, NULL);
 
-	o->source = odb_source_new(o, flags);
+	o->source = odb_source_new(o, repo->object_storage_format, flags);
 	o->inmemory_objects = &odb_source_inmemory_new(o)->base;
 
 	return o;
diff --git a/odb/source-files.c b/odb/source-files.c
index 455f409db0..4a7cbbfd9b 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -1235,6 +1235,7 @@ struct odb_files_dir *odb_source_files_find_dir(struct object_database *odb, con
 }
 
 struct odb_source_files *odb_source_files_new(struct object_database *odb,
+					      const char *payload,
 					      enum odb_new_flags flags)
 {
 	struct odb_source_files *files;
@@ -1245,6 +1246,12 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
 		object_dir = xstrdup_or_null(getenv(DB_ENVIRONMENT));
 		alternates = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));
 	}
+	if (!object_dir && payload) {
+		if (is_absolute_path(payload))
+			object_dir = xstrdup(payload);
+		else
+			object_dir = xstrfmt("%s/%s", odb->repo->commondir, payload);
+	}
 	if (!object_dir)
 		object_dir = xstrfmt("%s/objects", odb->repo->commondir);
 
diff --git a/odb/source-files.h b/odb/source-files.h
index 837e890304..71e8db84ff 100644
--- a/odb/source-files.h
+++ b/odb/source-files.h
@@ -70,6 +70,7 @@ struct odb_source_files {
 
 /* Allocate and initialize a new object source. */
 struct odb_source_files *odb_source_files_new(struct object_database *odb,
+					      const char *payload,
 					      enum odb_new_flags flags);
 
 /*
diff --git a/odb/source.c b/odb/source.c
index b25ef14df8..7917ecc990 100644
--- a/odb/source.c
+++ b/odb/source.c
@@ -1,4 +1,5 @@
 #include "git-compat-util.h"
+#include "gettext.h"
 #include "object-file.h"
 #include "odb/source-files.h"
 #include "odb/source.h"
@@ -23,10 +24,52 @@ const char *odb_source_type_to_name(enum odb_source_type type)
 	return name;
 }
 
+static enum odb_source_type odb_source_type_by_name(const char *name)
+{
+	if (!strcmp(name, "files"))
+		return ODB_SOURCE_FILES;
+	return ODB_SOURCE_UNKNOWN;
+}
+
+enum odb_source_type odb_source_type_by_uri(const char *uri, char **payload)
+{
+	enum odb_source_type type;
+	const char *schema_end;
+	char *name;
+
+	schema_end = strstr(uri, "://");
+	if (!schema_end) {
+		name = xstrdup(uri);
+		if (payload)
+			*payload = NULL;
+	} else {
+		name = xstrndup(uri, schema_end - uri);
+		if (payload)
+			*payload = *(schema_end + 3) ? xstrdup(schema_end + 3) : NULL;
+	}
+
+	type = odb_source_type_by_name(name);
+	free(name);
+	return type;
+}
+
 struct odb_source *odb_source_new(struct object_database *odb,
+				  const char *object_storage_format,
 				  enum odb_new_flags flags)
 {
-	return &odb_source_files_new(odb, flags)->base;
+	struct odb_source *source;
+	char *payload = NULL;
+
+	switch (odb_source_type_by_uri(object_storage_format, &payload)) {
+	case ODB_SOURCE_FILES:
+		source = &odb_source_files_new(odb, payload, flags)->base;
+		break;
+	default:
+		die(_("unknown object storage format '%s'"), object_storage_format);
+	}
+
+	free(payload);
+	return source;
 }
 
 void odb_source_init(struct odb_source *source,
diff --git a/odb/source.h b/odb/source.h
index 1867347dfc..c194b3254b 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -32,6 +32,14 @@ enum odb_source_type {
  */
 const char *odb_source_type_to_name(enum odb_source_type type);
 
+/*
+ * Parse the given URI in the format "<schema>[://<payload>]" and return the
+ * source type identified by the schema. If `payload` is non-NULL it will be
+ * set to a newly allocated copy of the payload, or NULL if the URI has no or
+ * an empty payload. Returns `ODB_SOURCE_UNKNOWN` for unknown schemas.
+ */
+enum odb_source_type odb_source_type_by_uri(const char *uri, char **payload);
+
 struct object_id;
 struct odb_stream;
 struct strbuf;
@@ -304,9 +312,11 @@ struct odb_source {
 
 /*
  * Allocate and initialize a new source for the given object database. The path
- * of the source is derived from repository paths.
+ * of the source is derived from repository paths or from the object storage
+ * format.
  */
 struct odb_source *odb_source_new(struct object_database *odb,
+				  const char *object_storage_format,
 				  enum odb_new_flags flags);
 
 /*
diff --git a/repository.c b/repository.c
index 970d181828..5b1f1460e0 100644
--- a/repository.c
+++ b/repository.c
@@ -217,6 +217,16 @@ void repo_set_ref_storage_format(struct repository *repo,
 	repo->ref_storage_payload = xstrdup_or_null(payload);
 }
 
+void repo_set_object_storage_format(struct repository *repo,
+				    const char *uri)
+{
+	free(repo->object_storage_format);
+	if (uri)
+		repo->object_storage_format = xstrdup(uri);
+	else
+		repo->object_storage_format = xstrdup("files");
+}
+
 /*
  * Attempt to resolve and set the provided 'gitdir' for repository 'repo'.
  * Return 0 upon success and a non-zero value upon failure.
@@ -382,6 +392,7 @@ void repo_clear(struct repository *repo)
 	free(repo->worktree);
 	free(repo->submodule_prefix);
 	free(repo->ref_storage_payload);
+	free(repo->object_storage_format);
 
 	odb_free(repo->objects);
 
diff --git a/repository.h b/repository.h
index 2a348012e8..e9a3a7f18b 100644
--- a/repository.h
+++ b/repository.h
@@ -178,6 +178,12 @@ struct repository {
 	 */
 	char *ref_storage_payload;
 
+	/*
+	 * The object storage URI in the format "<schema>[://<payload>]", as
+	 * configured via "extensions.objectStorage".
+	 */
+	char *object_storage_format;
+
 	/* A unique-id for tracing purposes. */
 	int trace2_repo_id;
 
@@ -255,6 +261,8 @@ void repo_set_compat_hash_algo(struct repository *repo, uint32_t compat_algo);
 void repo_set_ref_storage_format(struct repository *repo,
 				 enum ref_storage_format format,
 				 const char *payload);
+void repo_set_object_storage_format(struct repository *repo,
+				    const char *uri);
 void initialize_repository(struct repository *repo);
 RESULT_MUST_BE_USED
 int repo_init(struct repository *r, const char *gitdir, const char *worktree);
diff --git a/setup.c b/setup.c
index 3ee19f3e3c..499b5bbe3c 100644
--- a/setup.c
+++ b/setup.c
@@ -9,6 +9,7 @@
 #include "hex.h"
 #include "object-file.h"
 #include "object-name.h"
+#include "odb/source.h"
 #include "refs.h"
 #include "repository.h"
 #include "config.h"
@@ -687,6 +688,15 @@ static enum extension_result handle_extension(const char *var,
 				     "extensions.refstorage", value);
 		data->ref_storage_format = format;
 		return EXTENSION_OK;
+	} else if (!strcmp(ext, "objectstorage")) {
+		if (!value)
+			return config_error_nonbool(var);
+		if (odb_source_type_by_uri(value, NULL) == ODB_SOURCE_UNKNOWN)
+			return error(_("invalid value for '%s': '%s'"),
+				     "extensions.objectstorage", value);
+		free(data->object_storage);
+		data->object_storage = xstrdup(value);
+		return EXTENSION_OK;
 	} else if (!strcmp(ext, "relativeworktrees")) {
 		data->relative_worktrees = git_config_bool(var, value);
 		return EXTENSION_OK;
@@ -694,6 +704,7 @@ static enum extension_result handle_extension(const char *var,
 		data->submodule_path_cfg = git_config_bool(var, value);
 		return EXTENSION_OK;
 	}
+
 	return EXTENSION_UNKNOWN;
 }
 
@@ -864,6 +875,7 @@ void clear_repository_format(struct repository_format *format)
 	free(format->work_tree);
 	free(format->partial_clone);
 	free(format->ref_storage_payload);
+	free(format->object_storage);
 	init_repository_format(format);
 }
 
@@ -1786,6 +1798,7 @@ int apply_repository_format(struct repository *repo,
 	repo_set_ref_storage_format(repo,
 				    format->ref_storage_format,
 				    format->ref_storage_payload);
+	repo_set_object_storage_format(repo, format->object_storage);
 	repo->repository_format_worktree_config =
 		format->worktree_config;
 	repo->repository_format_submodule_path_cfg =
diff --git a/setup.h b/setup.h
index 7a3efe5e48..9145aacc5f 100644
--- a/setup.h
+++ b/setup.h
@@ -192,6 +192,7 @@ struct repository_format {
 	char *ref_storage_payload;
 	int sparse_index;
 	char *work_tree;
+	char *object_storage;
 	struct string_list unknown_extensions;
 	struct string_list v1_only_extensions;
 };
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index f1d227f271..c7c464d3a2 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -839,6 +839,36 @@ test_expect_success 'init with --ref-storage-format=garbage' '
 	test_cmp expect err
 '
 
+test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage is not allowed with repo version 0' '
+	test_when_finished "rm -rf objstorage" &&
+	git init objstorage &&
+	git -C objstorage config set extensions.objectStorage files &&
+	test_must_fail git -C objstorage rev-parse 2>err &&
+	test_grep "repo version is 0, but v1-only extension found" err
+'
+
+test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with files backend' '
+	test_when_finished "rm -rf objstorage" &&
+	git init objstorage &&
+	git -C objstorage config set core.repositoryformatversion 1 &&
+	git -C objstorage config set extensions.objectStorage files &&
+	test_commit -C objstorage A &&
+	git -C objstorage rev-parse --verify HEAD
+'
+
+test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with unknown backend' '
+	test_when_finished "rm -rf objstorage" &&
+	git init objstorage &&
+	git -C objstorage config set core.repositoryformatversion 1 &&
+	git -C objstorage config set extensions.objectStorage garbage &&
+	test_must_fail git -C objstorage rev-parse 2>err &&
+	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage${SQ}" err &&
+
+	git config --file objstorage/.git/config extensions.objectStorage garbage://payload &&
+	test_must_fail git -C objstorage rev-parse 2>err &&
+	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage://payload${SQ}" err
+'
+
 test_expect_success MINGW 'core.hidedotfiles = false' '
 	git config --global core.hidedotfiles false &&
 	rm -rf newdir &&
diff --git a/t/unit-tests/u-odb-inmemory.c b/t/unit-tests/u-odb-inmemory.c
index a8da278c68..d7261b9e0c 100644
--- a/t/unit-tests/u-odb-inmemory.c
+++ b/t/unit-tests/u-odb-inmemory.c
@@ -12,6 +12,7 @@
 
 static struct repository repo = {
 	.hash_algo = &hash_algos[GIT_HASH_SHA1],
+	.object_storage_format = (char *) "files",
 };
 static struct object_database *odb;
 

-- 
2.56.0.307.g5fe7bd680c.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v2 3/7] setup: allow overriding object storage format via environment
  2026-10-21  5:39 ` [PATCH v2 0/7] odb: introduce object storage extension Patrick Steinhardt
  2026-10-21  5:39   ` [PATCH v2 1/7] loose: drop unused `repo_write_loose_object_map()` Patrick Steinhardt
  2026-10-21  5:39   ` [PATCH v2 2/7] setup: introduce object storage extension Patrick Steinhardt
@ 2026-10-21  5:39   ` Patrick Steinhardt
  2026-10-21  5:39   ` [PATCH v2 4/7] builtin: expose the object storage format Patrick Steinhardt
                     ` (4 subsequent siblings)
  7 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-21  5:39 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

The ref storage format configured via "extensions.refStorage" can be
overridden with the GIT_REF_STORAGE_FORMAT environment variable.
Introduce the GIT_OBJECT_STORAGE_FORMAT environment variable that
overrides "extensions.objectStorage" in the same way.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git.adoc |  5 +++++
 environment.h          |  1 +
 setup.c                | 14 ++++++++++++++
 t/t0001-init.sh        | 26 ++++++++++++++++++++++++++
 4 files changed, 46 insertions(+)

diff --git a/Documentation/git.adoc b/Documentation/git.adoc
index 3abb4701c3..34812131bb 100644
--- a/Documentation/git.adoc
+++ b/Documentation/git.adoc
@@ -609,6 +609,11 @@ double-quotes and respecting backslash escapes. E.g., the value
 `GIT_REFERENCE_BACKEND`::
 	Deprecated alias of `GIT_REF_STORAGE_FORMAT`.
 
+`GIT_OBJECT_STORAGE_FORMAT`::
+	Specify which object storage format to use along with its URI.
+	See `extensions.objectStorage` option in linkgit:git-config[1] for more
+	details. Overrides the config variable when used.
+
 Git Commits
 ~~~~~~~~~~~
 `GIT_AUTHOR_NAME`::
diff --git a/environment.h b/environment.h
index b336459e90..c26ef92b52 100644
--- a/environment.h
+++ b/environment.h
@@ -45,6 +45,7 @@
 #define GIT_ATTR_SOURCE_ENVIRONMENT "GIT_ATTR_SOURCE"
 #define GIT_REFERENCE_BACKEND_ENVIRONMENT "GIT_REFERENCE_BACKEND"
 #define GIT_REF_STORAGE_FORMAT_ENVIRONMENT "GIT_REF_STORAGE_FORMAT"
+#define GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT "GIT_OBJECT_STORAGE_FORMAT"
 
 /*
  * Environment variable used to propagate the --no-advice global option to the
diff --git a/setup.c b/setup.c
index 499b5bbe3c..98140a7641 100644
--- a/setup.c
+++ b/setup.c
@@ -2073,6 +2073,7 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
 
 		if (startup_info->have_repository) {
 			struct strbuf err = STRBUF_INIT;
+			const char *object_storage_uri;
 			const char *ref_backend_uri;
 
 			/*
@@ -2091,6 +2092,19 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
 					die(_("unknown ref storage format: '%s'"), ref_backend_uri);
 			}
 
+			/*
+			 * Likewise, the GIT_OBJECT_STORAGE_FORMAT environment
+			 * variable should override the repository config for
+			 * 'extensions.objectStorage'.
+			 */
+			object_storage_uri = getenv(GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT);
+			if (object_storage_uri) {
+				if (odb_source_type_by_uri(object_storage_uri, NULL) == ODB_SOURCE_UNKNOWN)
+					die(_("unknown object storage format: '%s'"), object_storage_uri);
+				free(discovery.format.object_storage);
+				discovery.format.object_storage = xstrdup(object_storage_uri);
+			}
+
 			if (apply_repository_format(repo, &discovery.format,
 						    APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)
 				die("%s", err.buf);
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index c7c464d3a2..5e7c25e42c 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -869,6 +869,32 @@ test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with unknown b
 	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage://payload${SQ}" err
 '
 
+test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FORMAT overrides extensions.objectStorage' '
+	test_when_finished "rm -rf objstorage" &&
+	git init objstorage &&
+	(
+		cd objstorage &&
+		git config set core.repositoryformatversion 1 &&
+		git config set extensions.objectStorage files://objs &&
+		mkdir .git/objs .git/other &&
+
+		echo content >file &&
+		oid=$(git hash-object -w file) &&
+		test_path_is_file .git/objs/$(test_oid_to_path "$oid") &&
+		git cat-file -e "$oid" &&
+
+		test_must_fail env GIT_OBJECT_STORAGE_FORMAT=files://other git cat-file -e "$oid" &&
+		GIT_OBJECT_STORAGE_FORMAT=files://other git hash-object -w file &&
+		test_path_is_file .git/other/$(test_oid_to_path "$oid") &&
+
+		cat >expect <<-EOF &&
+		fatal: unknown object storage format: ${SQ}garbage${SQ}
+		EOF
+		test_must_fail env GIT_OBJECT_STORAGE_FORMAT=garbage git rev-parse 2>err &&
+		test_cmp expect err
+	)
+'
+
 test_expect_success MINGW 'core.hidedotfiles = false' '
 	git config --global core.hidedotfiles false &&
 	rm -rf newdir &&

-- 
2.56.0.307.g5fe7bd680c.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v2 4/7] builtin: expose the object storage format
  2026-10-21  5:39 ` [PATCH v2 0/7] odb: introduce object storage extension Patrick Steinhardt
                     ` (2 preceding siblings ...)
  2026-10-21  5:39   ` [PATCH v2 3/7] setup: allow overriding object storage format via environment Patrick Steinhardt
@ 2026-10-21  5:39   ` Patrick Steinhardt
  2026-10-21  5:39   ` [PATCH v2 5/7] builtin/init: introduce handling of " Patrick Steinhardt
                     ` (3 subsequent siblings)
  7 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-21  5:39 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

The ref storage format of a repository can be inspected via both
git-rev-parse(1)'s "--show-ref-storage-format" and git-repo(1)'s
"references.storageFormat" field. Introduce the equivalent options for
the object storage format, as well.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-repo.adoc      | 6 ++++++
 Documentation/git-rev-parse.adoc | 5 +++++
 builtin/repo.c                   | 8 ++++++++
 builtin/rev-parse.c              | 5 +++++
 t/t0001-init.sh                  | 5 ++++-
 t/t1900-repo-info.sh             | 3 +++
 6 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index cb8c1e7292..d2dc9b7a69 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -105,6 +105,12 @@ values that they return:
 `object.format`::
 	The object format (hash algorithm) used in the repository.
 
+`objects.storageFormat`::
+	The object storage format used in the repository, as configured via
+	`extensions.objectStorage`. The valid values are:
++
+include::object-storage-format.adoc[]
+
 `path.commondir.absolute`::
 	The canonical absolute path to the Git repository's common
 	directory (the shared `.git` directory containing objects,
diff --git a/Documentation/git-rev-parse.adoc b/Documentation/git-rev-parse.adoc
index 5b866af66a..9103d54d53 100644
--- a/Documentation/git-rev-parse.adoc
+++ b/Documentation/git-rev-parse.adoc
@@ -338,6 +338,11 @@ The following options are unaffected by `--path-format`:
 --show-ref-format::
 	Deprecated alias of `--show-ref-storage-format`.
 
+--show-object-storage-format::
+	Show the object storage format used for the repository. Prints the
+	value of `extensions.objectStorage`, or `files` when the extension is
+	not set.
+
 
 Other Options
 ~~~~~~~~~~~~~
diff --git a/builtin/repo.c b/builtin/repo.c
index 84e012f83f..451c940140 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -6,6 +6,7 @@
 #include "hash.h"
 #include "hex.h"
 #include "odb.h"
+#include "odb/source.h"
 #include "parse-options.h"
 #include "path.h"
 #include "path-walk.h"
@@ -128,11 +129,18 @@ static int get_references_format(struct repository *repo, struct strbuf *buf)
 	return 0;
 }
 
+static int get_object_storage_format(struct repository *repo, struct strbuf *buf)
+{
+	strbuf_addstr(buf, repo->object_storage_format);
+	return 0;
+}
+
 /* repo_info_field keys must be in lexicographical order */
 static const struct repo_info_field repo_info_field[] = {
 	{ "layout.bare", get_layout_bare },
 	{ "layout.shallow", get_layout_shallow },
 	{ "object.format", get_object_format },
+	{ "objects.storageFormat", get_object_storage_format },
 	{ "path.commondir.absolute", get_path_commondir_absolute },
 	{ "path.commondir.relative", get_path_commondir_relative },
 	{ "path.gitdir.absolute", get_path_gitdir_absolute },
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index ec33c19bdf..9dd692dcb6 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -17,6 +17,7 @@
 #include "gettext.h"
 #include "hash.h"
 #include "hex.h"
+#include "odb/source.h"
 #include "refs.h"
 #include "quote.h"
 #include "object-name.h"
@@ -1138,6 +1139,10 @@ int cmd_rev_parse(int argc,
 				puts(ref_storage_format_to_name(the_repository->ref_storage_format));
 				continue;
 			}
+			if (!strcmp(arg, "--show-object-storage-format")) {
+				puts(the_repository->object_storage_format);
+				continue;
+			}
 			if (!strcmp(arg, "--end-of-options")) {
 				seen_end_of_options = 1;
 				if (filter & (DO_FLAGS | DO_REVS))
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 5e7c25e42c..d080ab04a8 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -853,7 +853,10 @@ test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with files bac
 	git -C objstorage config set core.repositoryformatversion 1 &&
 	git -C objstorage config set extensions.objectStorage files &&
 	test_commit -C objstorage A &&
-	git -C objstorage rev-parse --verify HEAD
+	git -C objstorage rev-parse --verify HEAD &&
+	echo files >expect &&
+	git -C objstorage rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with unknown backend' '
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index d115d2d9f9..493a78f230 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -69,6 +69,9 @@ test_repo_info 'object.format = sha1 is retrieved correctly' \
 test_repo_info 'object.format = sha256 is retrieved correctly' \
 	'git init --object-format=sha256' 'sha256' 'object.format' 'sha256'
 
+test_repo_info 'objects.storageFormat = files is retrieved correctly' \
+	'git init' 'objects-files' 'objects.storageFormat' 'files'
+
 test_expect_success 'values returned in order requested' '
 	cat >expect <<-\EOF &&
 	layout.bare=false

-- 
2.56.0.307.g5fe7bd680c.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v2 5/7] builtin/init: introduce handling of object storage format
  2026-10-21  5:39 ` [PATCH v2 0/7] odb: introduce object storage extension Patrick Steinhardt
                     ` (3 preceding siblings ...)
  2026-10-21  5:39   ` [PATCH v2 4/7] builtin: expose the object storage format Patrick Steinhardt
@ 2026-10-21  5:39   ` Patrick Steinhardt
  2026-10-21 14:23     ` Karthik Nayak
  2026-10-21  5:39   ` [PATCH v2 6/7] builtin/clone: introduce "--object-storage-format=" Patrick Steinhardt
                     ` (2 subsequent siblings)
  7 siblings, 1 reply; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-21  5:39 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

While the object storage format extension can already be configured
manually via git-config(1), this isn't exactly an ergonomic way of doing
so. For the reference storage format extension, users have a host of
options available to them.

Introduce the equivalent options for the object storage format.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/config/init.adoc |   5 ++
 Documentation/git-init.adoc    |  10 ++-
 Documentation/git.adoc         |   5 ++
 builtin/clone.c                |   2 +-
 builtin/init-db.c              |   7 +-
 setup.c                        |  89 +++++++++++++++++++++-
 setup.h                        |   1 +
 t/t0001-init.sh                | 162 +++++++++++++++++++++++++++++++++++++++++
 8 files changed, 273 insertions(+), 8 deletions(-)

diff --git a/Documentation/config/init.adoc b/Documentation/config/init.adoc
index e45bd692db..dbd9c663ee 100644
--- a/Documentation/config/init.adoc
+++ b/Documentation/config/init.adoc
@@ -20,6 +20,11 @@ endif::[]
 	precedence over this config.
 `init.defaultRefFormat`::
 	Deprecated alias of `init.defaultRefStorageFormat`.
+`init.defaultObjectStorageFormat`::
+	Allows overriding the default object storage format for new
+	repositories. See `--object-storage-format=` in linkgit:git-init[1].
+	Both the command line option and the `GIT_DEFAULT_OBJECT_STORAGE_FORMAT`
+	environment variable take precedence over this config.
 
 init.defaultSubmodulePathConfig::
 	A boolean that specifies if `git init` and `git clone` should
diff --git a/Documentation/git-init.adoc b/Documentation/git-init.adoc
index b02af22ad1..9e022a7ba8 100644
--- a/Documentation/git-init.adoc
+++ b/Documentation/git-init.adoc
@@ -11,7 +11,7 @@ SYNOPSIS
 [synopsis]
 git init [-q | --quiet] [--bare] [--template=<template-directory>]
 	 [--separate-git-dir <git-dir>] [--object-format=<format>]
-	 [--ref-storage-format=<format>]
+	 [--ref-storage-format=<format>] [--object-storage-format=<format>]
 	 [-b <branch-name> | --initial-branch=<branch-name>]
 	 [--shared[=<permissions>]] [<directory>]
 
@@ -68,6 +68,14 @@ include::ref-storage-format.adoc[]
 `--ref-format=<format>`::
 Deprecated alias of `--ref-storage-format=<format>`.
 
+`--object-storage-format=<format>`::
+Specify the given object storage _<format>_ for the repository. Backends that
+require additional configuration accept a payload in the form
+`<format>://<payload>`, for example a path or a connection string identifying
+the database that shall store the objects. The valid values are:
++
+include::object-storage-format.adoc[]
+
 `--template=<template-directory>`::
 Specify the directory from which templates will be used.  (See the "TEMPLATE
 DIRECTORY" section below.)
diff --git a/Documentation/git.adoc b/Documentation/git.adoc
index 34812131bb..67de5646f0 100644
--- a/Documentation/git.adoc
+++ b/Documentation/git.adoc
@@ -609,6 +609,11 @@ double-quotes and respecting backslash escapes. E.g., the value
 `GIT_REFERENCE_BACKEND`::
 	Deprecated alias of `GIT_REF_STORAGE_FORMAT`.
 
+`GIT_DEFAULT_OBJECT_STORAGE_FORMAT`::
+	If this variable is set, the default object storage format for new
+	repositories will be set to this value. The default is "files".
+	See `--object-storage-format` in linkgit:git-init[1].
+
 `GIT_OBJECT_STORAGE_FORMAT`::
 	Specify which object storage format to use along with its URI.
 	See `extensions.objectStorage` option in linkgit:git-config[1] for more
diff --git a/builtin/clone.c b/builtin/clone.c
index 6047501dfc..ba4fc16def 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1214,7 +1214,7 @@ int cmd_clone(int argc,
 	 */
 	create_repository(the_repository, git_dir, real_git_dir, work_tree,
 			  option_template, GIT_HASH_UNKNOWN, ref_storage_format_uri,
-			  do_not_override_repo_unix_permissions, NULL);
+			  NULL, do_not_override_repo_unix_permissions, NULL);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index 0abdbca8e9..71b748f2f2 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -57,7 +57,7 @@ static int shared_callback(const struct option *opt, const char *arg, int unset)
 static const char *const init_db_usage[] = {
 	N_("git init [-q | --quiet] [--bare] [--template=<template-directory>]\n"
 	   "         [--separate-git-dir <git-dir>] [--object-format=<format>]\n"
-	   "         [--ref-storage-format=<format>]\n"
+	   "         [--ref-storage-format=<format>] [--object-storage-format=<format>]\n"
 	   "         [-b <branch-name> | --initial-branch=<branch-name>]\n"
 	   "         [--shared[=<permissions>]] [<directory>]"),
 	NULL
@@ -84,6 +84,7 @@ int cmd_init_db(int argc,
 	int bare = startup_info->force_bare_repository ? 1 : -1;
 	const char *object_format = NULL;
 	const char *ref_storage_format_uri = NULL;
+	const char *object_storage_format_uri = NULL;
 	const char *initial_branch = NULL;
 	int hash_algo = GIT_HASH_UNKNOWN;
 	int init_shared_repository = -1;
@@ -111,6 +112,8 @@ int cmd_init_db(int argc,
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format_uri, N_("format"),
 			   N_("specify the reference storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
+		OPT_STRING(0, "object-storage-format", &object_storage_format_uri, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_END()
 	};
 	int reinit;
@@ -232,7 +235,7 @@ int cmd_init_db(int argc,
 
 	create_repository(the_repository, git_dir, real_git_dir, work_tree,
 			  template_dir, hash_algo, ref_storage_format_uri,
-			  init_shared_repository, &reinit);
+			  object_storage_format_uri, init_shared_repository, &reinit);
 	create_reference_database(the_repository, initial_branch, quiet);
 	create_object_database(the_repository, NULL);
 
diff --git a/setup.c b/setup.c
index 98140a7641..3d74f8eeed 100644
--- a/setup.c
+++ b/setup.c
@@ -2466,7 +2466,8 @@ void initialize_repository_version(struct repository *repo,
 	 */
 	if (hash_algo != GIT_HASH_SHA1_LEGACY ||
 	    ref_storage_format != REF_STORAGE_FORMAT_FILES ||
-	    repo->ref_storage_payload)
+	    repo->ref_storage_payload ||
+	    strcmp(repo->object_storage_format, "files"))
 		target_version = GIT_REPO_VERSION_READ;
 
 	if (hash_algo != GIT_HASH_SHA1_LEGACY && hash_algo != GIT_HASH_UNKNOWN)
@@ -2490,6 +2491,12 @@ void initialize_repository_version(struct repository *repo,
 		repo_config_set_gently(repo, "extensions.refstorage", NULL);
 	}
 
+	if (strcmp(repo->object_storage_format, "files"))
+		repo_config_set(repo, "extensions.objectstorage",
+				repo->object_storage_format);
+	else if (reinit)
+		repo_config_set_gently(repo, "extensions.objectstorage", NULL);
+
 	if (reinit) {
 		struct strbuf config = STRBUF_INIT;
 		struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
@@ -2726,6 +2733,7 @@ static void separate_git_dir(struct repository *repo,
 struct default_format_config {
 	int hash;
 	enum ref_storage_format ref_storage_format;
+	char *object_storage_format;
 };
 
 static int read_default_format_config(const char *key, const char *value,
@@ -2757,6 +2765,20 @@ static int read_default_format_config(const char *key, const char *value,
 		goto out;
 	}
 
+	if (!strcmp(key, "init.defaultobjectstorageformat")) {
+		ret = git_config_string(&str, key, value);
+		if (ret)
+			goto out;
+		if (odb_source_type_by_uri(str, NULL) == ODB_SOURCE_UNKNOWN) {
+			warning(_("unknown object storage format '%s'"), str);
+			goto out;
+		}
+		free(cfg->object_storage_format);
+		cfg->object_storage_format = str;
+		str = NULL;
+		goto out;
+	}
+
 	/*
 	 * Enable the reftable format when "features.experimental" is enabled.
 	 * "init.defaultRefStorageFormat" takes precedence over this setting.
@@ -2777,7 +2799,8 @@ static int read_default_format_config(const char *key, const char *value,
 
 static void repository_format_configure(struct repository_format *repo_fmt,
 					int hash,
-					const char *ref_storage_format_uri)
+					const char *ref_storage_format_uri,
+					const char *object_storage_uri)
 {
 	struct default_format_config cfg = {
 		.hash = GIT_HASH_UNKNOWN,
@@ -2790,6 +2813,7 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 	};
 	enum ref_storage_format ref_storage_format;
 	char *ref_storage_payload = NULL;
+	char *object_storage = NULL;
 	const char *env;
 
 	config_with_options(read_default_format_config, &cfg, NULL, NULL, &opts);
@@ -2877,9 +2901,58 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 		ref_storage_format = REF_STORAGE_FORMAT_DEFAULT;
 	}
 
+	/*
+	 * Determine the object storage format, with the same precedence as
+	 * for the ref storage format:
+	 *
+	 *   1. The format specified via the command line.
+	 *
+	 *   2. The format specified via "GIT_OBJECT_STORAGE_FORMAT".
+	 *
+	 *   3. The format of a preexisting repository when reinitializing.
+	 *
+	 *   4. The default format for new repositories as configured via
+	 *      "GIT_DEFAULT_OBJECT_STORAGE_FORMAT".
+	 *
+	 *   5. The default format for new repositories as configured via
+	 *      "init.defaultObjectStorageFormat".
+	 *
+	 *   6. The "files" format.
+	 */
+	if (object_storage_uri) {
+		if (odb_source_type_by_uri(object_storage_uri, NULL) == ODB_SOURCE_UNKNOWN)
+			die(_("unknown object storage format '%s'"), object_storage_uri);
+		object_storage = xstrdup(object_storage_uri);
+	} else if ((env = getenv(GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT))) {
+		if (odb_source_type_by_uri(env, NULL) == ODB_SOURCE_UNKNOWN)
+			die(_("unknown object storage format specified via %s: '%s'"),
+			    GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT, env);
+		object_storage = xstrdup(env);
+	} else if (repo_fmt->version >= 0) {
+		object_storage = xstrdup_or_null(repo_fmt->object_storage);
+	} else if ((env = getenv("GIT_DEFAULT_OBJECT_STORAGE_FORMAT"))) {
+		if (odb_source_type_by_uri(env, NULL) == ODB_SOURCE_UNKNOWN)
+			die(_("unknown object storage format specified via %s: '%s'"),
+			    "GIT_DEFAULT_OBJECT_STORAGE_FORMAT", env);
+		object_storage = xstrdup(env);
+	} else {
+		object_storage = xstrdup_or_null(cfg.object_storage_format);
+	}
+
+	/*
+	 * The "files" format without a payload is the default and thus doesn't
+	 * need to be recorded as an extension.
+	 */
+	if (object_storage) {
+		char *payload = NULL;
+		if (odb_source_type_by_uri(object_storage, &payload) == ODB_SOURCE_FILES && !payload)
+			FREE_AND_NULL(object_storage);
+		free(payload);
+	}
+
 	/*
 	 * If we have a preexisting repository we need to verify that its
-	 * current ref storage format does not change.
+	 * current storage formats do not change.
 	 */
 	if (repo_fmt->version >= 0) {
 		if (ref_storage_format != repo_fmt->ref_storage_format)
@@ -2888,11 +2961,18 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 		    strcmp(ref_storage_payload ? ref_storage_payload : "",
 			   repo_fmt->ref_storage_payload ? repo_fmt->ref_storage_payload : ""))
 			die(_("attempt to reinitialize repository with different reference storage payload"));
+		if (strcmp(object_storage ? object_storage : "",
+			   repo_fmt->object_storage ? repo_fmt->object_storage : ""))
+			die(_("attempt to reinitialize repository with different object storage format"));
 	}
 
 	free(repo_fmt->ref_storage_payload);
 	repo_fmt->ref_storage_format = ref_storage_format;
 	repo_fmt->ref_storage_payload = ref_storage_payload;
+
+	free(repo_fmt->object_storage);
+	repo_fmt->object_storage = object_storage;
+	free(cfg.object_storage_format);
 }
 
 void create_repository(struct repository *repo,
@@ -2902,6 +2982,7 @@ void create_repository(struct repository *repo,
 		       const char *template_dir,
 		       int hash,
 		       const char *ref_storage_format_uri,
+		       const char *object_storage_format_uri,
 		       int init_shared_repository,
 		       int *reinit_ok)
 {
@@ -2940,7 +3021,7 @@ void create_repository(struct repository *repo,
 	 * is an attempt to reinitialize new repository with an old tool.
 	 */
 	read_and_verify_repository_format(&repo_fmt, repo_get_git_dir(repo), NULL);
-	repository_format_configure(&repo_fmt, hash, ref_storage_format_uri);
+	repository_format_configure(&repo_fmt, hash, ref_storage_format_uri, object_storage_format_uri);
 	if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)
 		die("%s", err.buf);
 
diff --git a/setup.h b/setup.h
index 9145aacc5f..4e1737ade9 100644
--- a/setup.h
+++ b/setup.h
@@ -275,6 +275,7 @@ void create_repository(struct repository *repo,
 		       const char *template_dir,
 		       int hash_algo,
 		       const char *ref_storage_format_uri,
+		       const char *object_storage_format_uri,
 		       int init_shared_repository,
 		       int *reinit_ok);
 
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index d080ab04a8..0da14e8f70 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -898,6 +898,168 @@ test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FORMAT overrides ext
 	)
 '
 
+test_expect_success 'init with --object-storage-format=files does not set extension' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files objformat &&
+	test_must_fail git -C objformat config get extensions.objectstorage &&
+	echo files >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual &&
+	if test_have_prereq DEFAULT_REPO_FORMAT
+	then
+		echo 0 >expect &&
+		git -C objformat config get core.repositoryformatversion >actual &&
+		test_cmp expect actual
+	fi
+'
+
+test_expect_success 'init with --object-storage-format=files:// does not set extension' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files:// objformat &&
+	test_must_fail git -C objformat config get extensions.objectstorage &&
+	echo files >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'init with --object-storage-format=files://<relative-path>' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files://objs objformat &&
+	echo files://objs >expect &&
+	git -C objformat config get extensions.objectstorage >actual &&
+	test_cmp expect actual &&
+	echo 1 >expect &&
+	git -C objformat config get core.repositoryformatversion >actual &&
+	test_cmp expect actual &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	echo files://objs >expect &&
+	test_cmp expect actual &&
+	test_path_is_dir objformat/.git/objects &&
+	test_path_is_dir objformat/.git/objs &&
+	test_commit -C objformat A &&
+	git -C objformat rev-parse --verify HEAD &&
+	test_path_is_missing objformat/.git/objects/pack &&
+	test_path_is_dir objformat/.git/objs/pack
+'
+
+test_expect_success 'init with --object-storage-format=files://<absolute-path>' '
+	test_when_finished "rm -rf objformat objdir" &&
+	git init --object-storage-format=files://"$(pwd)/objdir" objformat &&
+	echo "files://$(pwd)/objdir" >expect &&
+	git -C objformat config get extensions.objectstorage >actual &&
+	test_cmp expect actual &&
+	test_commit -C objformat A &&
+	git -C objformat rev-parse --verify HEAD &&
+	test_path_is_dir objdir/pack
+'
+
+test_expect_success 'GIT_OBJECT_DIRECTORY takes precedence over extensions.objectStorage payload' '
+	test_when_finished "rm -rf objformat objdir" &&
+	git init --object-storage-format=files://objs objformat &&
+	test_commit -C objformat A &&
+	mkdir objdir &&
+	test_must_fail env GIT_DIR="$PWD/objformat/.git" GIT_OBJECT_DIRECTORY="$PWD/objdir" \
+		git cat-file -e HEAD &&
+	GIT_DIR="$PWD/objformat/.git" GIT_OBJECT_DIRECTORY="$PWD/objdir" \
+		git hash-object -w objformat/A.t >oid &&
+	test_path_is_file objdir/$(test_oid_to_path "$(cat oid)")
+'
+
+test_expect_success 'init with --object-storage-format=garbage' '
+	test_when_finished "rm -rf objformat" &&
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format ${SQ}garbage${SQ}
+	EOF
+	test_must_fail git init --object-storage-format=garbage objformat 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'init with GIT_DEFAULT_OBJECT_STORAGE_FORMAT=garbage' '
+	test_when_finished "rm -rf objformat" &&
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format specified via GIT_DEFAULT_OBJECT_STORAGE_FORMAT: ${SQ}garbage${SQ}
+	EOF
+	test_must_fail env GIT_DEFAULT_OBJECT_STORAGE_FORMAT=garbage git init objformat 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'init with GIT_DEFAULT_OBJECT_STORAGE_FORMAT=files://objs' '
+	test_when_finished "rm -rf objformat" &&
+	GIT_DEFAULT_OBJECT_STORAGE_FORMAT=files://objs git init objformat &&
+	echo files://objs >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'init with GIT_OBJECT_STORAGE_FORMAT=garbage' '
+	test_when_finished "rm -rf objformat" &&
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format specified via GIT_OBJECT_STORAGE_FORMAT: ${SQ}garbage${SQ}
+	EOF
+	test_must_fail env GIT_OBJECT_STORAGE_FORMAT=garbage git init objformat 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'GIT_OBJECT_STORAGE_FORMAT takes precedence over default' '
+	test_when_finished "rm -rf objformat" &&
+	GIT_DEFAULT_OBJECT_STORAGE_FORMAT=files://default \
+	GIT_OBJECT_STORAGE_FORMAT=files://explicit git init objformat &&
+	echo files://explicit >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '--object-storage-format= takes precedence over environment' '
+	test_when_finished "rm -rf objformat" &&
+	GIT_OBJECT_STORAGE_FORMAT=files://explicit \
+	git init --object-storage-format=files://cmdline objformat &&
+	echo files://cmdline >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 're-init refuses with different storage format' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files objformat &&
+	cat >expect <<-EOF &&
+	fatal: attempt to reinitialize repository with different object storage format
+	EOF
+	test_must_fail env GIT_OBJECT_STORAGE_FORMAT=files://objs git init objformat 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 're-init with same object storage format' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files://objs objformat &&
+	git init --object-storage-format=files://objs objformat &&
+	git init objformat &&
+	echo files://objs >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'init warns about invalid init.defaultObjectStorageFormat' '
+	test_when_finished "rm -rf objformat" &&
+	test_config_global init.defaultObjectStorageFormat garbage &&
+	echo "warning: unknown object storage format ${SQ}garbage${SQ}" >expect &&
+	git init objformat 2>err &&
+	test_cmp expect err &&
+	echo files >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'init with init.defaultObjectStorageFormat' '
+	test_when_finished "rm -rf objformat" &&
+	test_config_global init.defaultObjectStorageFormat files://objs &&
+	git init objformat &&
+	echo files://objs >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual &&
+	test_commit -C objformat A &&
+	test_path_is_dir objformat/.git/objs/pack
+'
+
 test_expect_success MINGW 'core.hidedotfiles = false' '
 	git config --global core.hidedotfiles false &&
 	rm -rf newdir &&

-- 
2.56.0.307.g5fe7bd680c.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v2 6/7] builtin/clone: introduce "--object-storage-format="
  2026-10-21  5:39 ` [PATCH v2 0/7] odb: introduce object storage extension Patrick Steinhardt
                     ` (4 preceding siblings ...)
  2026-10-21  5:39   ` [PATCH v2 5/7] builtin/init: introduce handling of " Patrick Steinhardt
@ 2026-10-21  5:39   ` Patrick Steinhardt
  2026-10-21  5:39   ` [PATCH v2 7/7] builtin/submodule: propagate "--object-storage-format=" Patrick Steinhardt
  2026-10-21 14:31   ` [PATCH v2 0/7] odb: introduce object storage extension Karthik Nayak
  7 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-21  5:39 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

Wire up the new object storage format in git-clone(1) via a new
`--object-storage-format=` option, same as we have introduced in the
preceding commit for git-init(1).

Note that "--local" clones will not work if either of the object
databases is not using the "files" format. This is because we hardlink
loose objects and packfiles into the new repository, and that is
something that simply won't work with alternative backends.
Consequently, we either downgrade to a non-local clone with a warning,
or we die in case the user has explicitly asked for a local clone.

We _could_ in theory handle the case where the "files" backend has been
configured with an alternative object directory path (that is, with
"files:///path/to/dir"). But that's left for a future patch series to
implement for the sake of simplicity.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-clone.adoc | 11 +++++++
 builtin/clone.c              | 50 ++++++++++++++++++++++++++++++--
 t/t5601-clone.sh             | 68 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 127 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-clone.adoc b/Documentation/git-clone.adoc
index 07f7cf1a98..dd3b44817a 100644
--- a/Documentation/git-clone.adoc
+++ b/Documentation/git-clone.adoc
@@ -360,6 +360,17 @@ include::ref-storage-format.adoc[]
 `--ref-format=<format>`::
 Deprecated alias of `--ref-storage-format=<format>`.
 
+`--object-storage-format=<format>`::
+Specify the given object storage _<format>_ for the repository. Backends that
+require additional configuration accept a payload in the form
+`<format>://<payload>`, for example a path or a connection string identifying
+the database that shall store the objects. The valid values are:
++
+include::object-storage-format.adoc[]
++
+Local clones (see `--local`) fall back to the regular transport when either
+repository uses a non-default object storage.
+
 `-j<n>`::
 `--jobs=<n>`::
 	The number of submodules fetched at the same time.
diff --git a/builtin/clone.c b/builtin/clone.c
index ba4fc16def..85c124dc37 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -26,6 +26,7 @@
 #include "refspec.h"
 #include "object-file.h"
 #include "odb.h"
+#include "odb/source.h"
 #include "tree.h"
 #include "tree-walk.h"
 #include "unpack-trees.h"
@@ -378,6 +379,28 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 	dir_iterator_free(iter);
 }
 
+static bool is_using_default_object_storage_format(const char *repo_path)
+{
+	struct repository_format format = REPOSITORY_FORMAT_INIT;
+	struct strbuf config = STRBUF_INIT;
+	bool uses_default_format = true;
+	char *payload = NULL;
+
+	get_common_dir(&config, repo_path);
+	strbuf_addstr(&config, "/config");
+	read_repository_format(&format, config.buf);
+
+	if (format.version >= 1 &&
+	    format.object_storage &&
+	    (odb_source_type_by_uri(format.object_storage, &payload) != ODB_SOURCE_FILES || payload))
+		uses_default_format = false;
+
+	clear_repository_format(&format);
+	strbuf_release(&config);
+	free(payload);
+	return uses_default_format;
+}
+
 static void clone_local(const char *src_repo, const char *dest_repo)
 {
 	if (!option_shared) {
@@ -927,6 +950,7 @@ int cmd_clone(int argc,
 	struct string_list option_not = STRING_LIST_INIT_NODUP;
 	const char *real_git_dir = NULL;
 	const char *ref_storage_format_uri = NULL;
+	const char *object_storage_format_uri = NULL;
 	const char *option_upload_pack = "git-upload-pack";
 	int option_progress = -1;
 	int option_sparse_checkout = 0;
@@ -1008,6 +1032,8 @@ int cmd_clone(int argc,
 			   N_("separate git dir from working tree")),
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format_uri, N_("format"),
 			   N_("specify the reference storage format to use")),
+		OPT_STRING(0, "object-storage-format", &object_storage_format_uri, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
 		OPT_STRING_LIST('c', "config", &option_config, N_("key=value"),
 				N_("set config inside the new repository")),
@@ -1060,6 +1086,10 @@ int cmd_clone(int argc,
 			die(_("unknown ref storage format '%s'"), ref_storage_format_uri);
 	}
 
+	if (object_storage_format_uri &&
+	    odb_source_type_by_uri(object_storage_format_uri, NULL) == ODB_SOURCE_UNKNOWN)
+		die(_("unknown object storage format '%s'"), object_storage_format_uri);
+
 	if (option_mirror) {
 		option_bare = 1;
 		option_tags = 0;
@@ -1213,8 +1243,9 @@ int cmd_clone(int argc,
 	 * their on-disk data structures.
 	 */
 	create_repository(the_repository, git_dir, real_git_dir, work_tree,
-			  option_template, GIT_HASH_UNKNOWN, ref_storage_format_uri,
-			  NULL, do_not_override_repo_unix_permissions, NULL);
+			  option_template, GIT_HASH_UNKNOWN,
+			  ref_storage_format_uri, object_storage_format_uri,
+			  do_not_override_repo_unix_permissions, NULL);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
@@ -1362,6 +1393,21 @@ int cmd_clone(int argc,
 			is_local = 0;
 		}
 	}
+
+	/*
+	 * Local clones copy or hardlink the object directory of the source repository
+	 * into the destination. This only works when both repositories use the "files"
+	 * object storage format.
+	 */
+	if (is_local &&
+	    (!is_using_default_object_storage_format(path) || !is_using_default_object_storage_format(git_dir))) {
+		if (option_local > 0)
+			die(_("cannot perform local clone with non-default object storage format"));
+		else
+			warning(_("falling back to non-local clone because of non-default object storage format"));
+		is_local = 0;
+	}
+
 	if (option_local > 0 && !is_local)
 		warning(_("--local is ignored"));
 
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 06f6121f82..85f439d6b6 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -181,6 +181,74 @@ test_expect_success 'clone with garbage ref format' '
 	test_path_is_missing ref-storage
 '
 
+test_expect_success 'clone with files object storage format' '
+	test_when_finished "rm -rf object-storage" &&
+	git clone --object-storage-format=files --bare src object-storage &&
+	test_must_fail git -C object-storage config get extensions.objectstorage &&
+	git -C object-storage rev-parse --show-object-storage-format >actual &&
+	echo files >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'clone with object storage payload' '
+	test_when_finished "rm -rf object-storage" &&
+	git clone --object-storage-format=files://objs --bare src object-storage &&
+	git -C object-storage config get extensions.objectstorage >actual &&
+	echo files://objs >expect &&
+	test_cmp expect actual &&
+	git -C object-storage rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual &&
+	test_path_is_dir object-storage/objs/pack &&
+	test_path_is_missing object-storage/objects/pack
+'
+
+test_expect_success 'clone with object storage payload and bundle URI' '
+	test_when_finished "rm -rf object-storage src.bundle" &&
+	git -C src bundle create ../src.bundle --all &&
+	git clone --object-storage-format=files://objs --bundle-uri="$PWD/src.bundle" \
+		src object-storage &&
+	git -C object-storage rev-parse --show-object-storage-format >actual &&
+	echo files://objs >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'clone with garbage object storage format' '
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format ${SQ}garbage${SQ}
+	EOF
+	test_must_fail git clone --object-storage-format=garbage --bare src object-storage 2>err &&
+	test_cmp expect err &&
+	test_path_is_missing object-storage
+'
+
+test_expect_success 'local clone into non-default object storage bails' '
+	test_when_finished "rm -rf object-storage" &&
+	test_must_fail git clone --local --object-storage-format=files://objs src object-storage 2>err &&
+	test_grep "cannot perform local clone with non-default object storage format" err
+'
+
+test_expect_success 'local clone from explicit files object storage succeeds' '
+	test_when_finished "rm -rf object-storage object-storage-dest" &&
+	git clone src object-storage &&
+	git -C object-storage config set core.repositoryformatversion 1 &&
+	git -C object-storage config set extensions.objectstorage files &&
+	git clone --local object-storage object-storage-dest
+'
+
+test_expect_success 'local clone from non-default object storage bails' '
+	test_when_finished "rm -rf object-storage object-storage-dest" &&
+	git clone --object-storage-format=files://objs src object-storage &&
+	test_must_fail git clone --local object-storage object-storage-dest 2>err &&
+	test_grep "cannot perform local clone with non-default object storage format" err
+'
+
+test_expect_success 'local clone from non-default object storage without --local warns' '
+	test_when_finished "rm -rf object-storage object-storage-dest" &&
+	git clone --object-storage-format=files://objs src object-storage &&
+	git clone object-storage object-storage-dest 2>err &&
+	test_grep "falling back to non-local clone" err
+'
+
 test_expect_success 'clone to destination with trailing /' '
 
 	git clone src target-1/ &&

-- 
2.56.0.307.g5fe7bd680c.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v2 7/7] builtin/submodule: propagate "--object-storage-format="
  2026-10-21  5:39 ` [PATCH v2 0/7] odb: introduce object storage extension Patrick Steinhardt
                     ` (5 preceding siblings ...)
  2026-10-21  5:39   ` [PATCH v2 6/7] builtin/clone: introduce "--object-storage-format=" Patrick Steinhardt
@ 2026-10-21  5:39   ` Patrick Steinhardt
  2026-10-21 14:31   ` [PATCH v2 0/7] odb: introduce object storage extension Karthik Nayak
  7 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-21  5:39 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

The ref storage format passed to git-clone(1) propagates to recursively
cloned submodules, and git-submodule(1)'s "add" and "update" subcommands
accept `--ref-storage-format=` so that newly cloned submodules can be
set up with a specific format.

Teach git-submodule(1) to also propagate the object storage format.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-submodule.adoc           | 10 +++-
 builtin/clone.c                            | 10 +++-
 builtin/submodule--helper.c                | 28 +++++++++
 git-submodule.sh                           | 18 ++++++
 t/meson.build                              |  1 +
 t/t7427-submodule-object-storage-format.sh | 91 ++++++++++++++++++++++++++++++
 6 files changed, 154 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-submodule.adoc b/Documentation/git-submodule.adoc
index e9c81a7170..57a21bc8d9 100644
--- a/Documentation/git-submodule.adoc
+++ b/Documentation/git-submodule.adoc
@@ -34,7 +34,7 @@ COMMANDS
 With no arguments, shows the status of existing submodules.  Several
 subcommands are available to perform operations on the submodules.
 
-`add [-b <branch>] [-f | --force] [--name <name>] [--reference <repository>] [--ref-storage-format <format>] [--depth <depth>] [--] <repository> [<path>]`::
+`add [-b <branch>] [-f | --force] [--name <name>] [--reference <repository>] [--ref-storage-format <format>] [--object-storage-format <format>] [--depth <depth>] [--] <repository> [<path>]`::
 	Add the given repository as a submodule at the given path
 	to the changeset to be committed next to the current
 	project: the current project is termed the "superproject".
@@ -75,6 +75,9 @@ URL in `.gitmodules`.
 If `--ref-storage-format <format>` or its deprecated alias `--ref-format
 <format>` is specified, the ref storage format of the newly cloned submodules
 will be set accordingly.
++
+If `--object-storage-format <format>` is specified, the object storage format
+of the newly cloned submodules will be set accordingly.
 
 `status [--cached] [--recursive] [--] [<path>...]`::
 	Show the status of the submodules. This will print the SHA-1 of the
@@ -140,7 +143,7 @@ If you really want to remove a submodule from the repository and commit
 that use linkgit:git-rm[1] instead. See linkgit:gitsubmodules[7] for removal
 options.
 
-`update [--init] [--remote] [-N | --no-fetch] [--[no-]recommend-shallow] [-f | --force] [--checkout | --rebase | --merge] [--reference=<repository>] [--ref-storage-format=<format>] [--depth=<depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter=<filter-spec>] [--] [<path>...]`::
+`update [--init] [--remote] [-N | --no-fetch] [--[no-]recommend-shallow] [-f | --force] [--checkout | --rebase | --merge] [--reference=<repository>] [--ref-storage-format=<format>] [--object-storage-format=<format>] [--depth=<depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter=<filter-spec>] [--] [<path>...]`::
 +
 --
 Update the registered submodules to match what the superproject
@@ -193,6 +196,9 @@ If `--ref-storage-format <format>` or its deprecated alias `--ref-format
 <format>` is specified, the ref storage format of the newly cloned submodules
 will be set accordingly.
 
+If `--object-storage-format <format>` is specified, the object storage format
+of the newly cloned submodules will be set accordingly.
+
 If `--filter <filter-spec>` is specified, the given partial clone filter will be
 applied to the submodule. See linkgit:git-rev-list[1] for details on filter
 specifications.
diff --git a/builtin/clone.c b/builtin/clone.c
index 85c124dc37..63348c3f34 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -686,7 +686,8 @@ static int git_sparse_checkout_init(const char *repo)
 static int checkout(int submodule_progress,
 		    struct list_objects_filter_options *filter_options,
 		    int filter_submodules,
-		    enum ref_storage_format ref_storage_format)
+		    enum ref_storage_format ref_storage_format,
+		    const char *object_storage_format_uri)
 {
 	struct object_id oid;
 	char *head;
@@ -776,6 +777,10 @@ static int checkout(int submodule_progress,
 			strvec_pushf(&cmd.args, "--ref-storage-format=%s",
 				     ref_storage_format_to_name(ref_storage_format));
 
+		if (object_storage_format_uri)
+			strvec_pushf(&cmd.args, "--object-storage-format=%s",
+				     object_storage_format_uri);
+
 		if (filter_submodules && filter_options->choice)
 			strvec_pushf(&cmd.args, "--filter=%s",
 				     expand_list_objects_filter_spec(filter_options));
@@ -1703,7 +1708,8 @@ int cmd_clone(int argc,
 	err = checkout(submodule_progress,
 		       &filter_options,
 		       filter_submodules,
-		       ref_storage_format);
+		       ref_storage_format,
+		       object_storage_format_uri);
 
 	list_objects_filter_release(&filter_options);
 
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 64412adf9f..8a9c9e17b6 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1739,6 +1739,7 @@ struct module_clone_data {
 	int depth;
 	struct list_objects_filter_options *filter_options;
 	enum ref_storage_format ref_storage_format;
+	const char *object_storage_format;
 	unsigned int quiet: 1;
 	unsigned int progress: 1;
 	unsigned int dissociate: 1;
@@ -1948,6 +1949,9 @@ static int clone_submodule(const struct module_clone_data *clone_data,
 		if (clone_data->ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
 			strvec_pushf(&cp.args, "--ref-storage-format=%s",
 				     ref_storage_format_to_name(clone_data->ref_storage_format));
+		if (clone_data->object_storage_format)
+			strvec_pushf(&cp.args, "--object-storage-format=%s",
+				     clone_data->object_storage_format);
 		if (clone_data->dissociate)
 			strvec_push(&cp.args, "--dissociate");
 		if (sm_gitdir && *sm_gitdir)
@@ -2065,6 +2069,8 @@ static int module_clone(int argc, const char **argv, const char *prefix,
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
 			   N_("specify the reference storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
+		OPT_STRING(0, "object-storage-format", &clone_data.object_storage_format, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_BOOL(0, "dissociate", &dissociate,
 			   N_("use --reference only while cloning")),
 		OPT_INTEGER(0, "depth", &clone_data.depth,
@@ -2095,6 +2101,9 @@ static int module_clone(int argc, const char **argv, const char *prefix,
 		if (clone_data.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
 			die(_("unknown ref storage format '%s'"), ref_storage_format);
 	}
+	if (clone_data.object_storage_format &&
+	    odb_source_type_by_uri(clone_data.object_storage_format, NULL) == ODB_SOURCE_UNKNOWN)
+		die(_("unknown object storage format '%s'"), clone_data.object_storage_format);
 	clone_data.dissociate = !!dissociate;
 	clone_data.quiet = !!quiet;
 	clone_data.progress = !!progress;
@@ -2202,6 +2211,7 @@ struct update_data {
 	struct list_objects_filter_options *filter_options;
 	struct module_list list;
 	enum ref_storage_format ref_storage_format;
+	const char *object_storage_format;
 	int depth;
 	int max_jobs;
 	int single_branch;
@@ -2365,6 +2375,9 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
 	if (suc->update_data->ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
 		strvec_pushf(&child->args, "--ref-storage-format=%s",
 			     ref_storage_format_to_name(suc->update_data->ref_storage_format));
+	if (suc->update_data->object_storage_format)
+		strvec_pushf(&child->args, "--object-storage-format=%s",
+			     suc->update_data->object_storage_format);
 	strvec_pushl(&child->args, "--path", sub->path, NULL);
 	strvec_pushl(&child->args, "--name", sub->name, NULL);
 	strvec_pushl(&child->args, "--url", url, NULL);
@@ -2809,6 +2822,9 @@ static void update_data_to_args(const struct update_data *update_data,
 	if (update_data->ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
 		strvec_pushf(args, "--ref-storage-format=%s",
 			     ref_storage_format_to_name(update_data->ref_storage_format));
+	if (update_data->object_storage_format)
+		strvec_pushf(args, "--object-storage-format=%s",
+			     update_data->object_storage_format);
 	if (update_data->filter_options && update_data->filter_options->choice)
 		strvec_pushf(args, "--filter=%s",
 				expand_list_objects_filter_spec(
@@ -3019,6 +3035,8 @@ static int module_update(int argc, const char **argv, const char *prefix,
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
 			   N_("specify the reference storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
+		OPT_STRING(0, "object-storage-format", &opt.object_storage_format, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_BOOL(0, "dissociate", &opt.dissociate,
 			   N_("use --reference only while cloning")),
 		OPT_INTEGER(0, "depth", &opt.depth,
@@ -3067,6 +3085,9 @@ static int module_update(int argc, const char **argv, const char *prefix,
 		if (opt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
 			die(_("unknown ref storage format '%s'"), ref_storage_format);
 	}
+	if (opt.object_storage_format &&
+	    odb_source_type_by_uri(opt.object_storage_format, NULL) == ODB_SOURCE_UNKNOWN)
+		die(_("unknown object storage format '%s'"), opt.object_storage_format);
 
 	opt.filter_options = &filter_options;
 	opt.prefix = prefix;
@@ -3372,6 +3393,7 @@ struct add_data {
 	const char *repo;
 	const char *realrepo;
 	enum ref_storage_format ref_storage_format;
+	const char *object_storage_format;
 	int depth;
 	unsigned int force: 1;
 	unsigned int quiet: 1;
@@ -3477,6 +3499,7 @@ static int add_submodule(const struct add_data *add_data)
 			string_list_append(&reference, p)->util = p;
 		}
 		clone_data.ref_storage_format = add_data->ref_storage_format;
+		clone_data.object_storage_format = add_data->object_storage_format;
 		clone_data.dissociate = add_data->dissociate;
 		if (add_data->depth >= 0)
 			clone_data.depth = add_data->depth;
@@ -3669,6 +3692,8 @@ static int module_add(int argc, const char **argv, const char *prefix,
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
 			   N_("specify the reference storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
+		OPT_STRING(0, "object-storage-format", &add_data.object_storage_format, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_BOOL(0, "dissociate", &dissociate, N_("borrow the objects from reference repositories")),
 		OPT_STRING(0, "name", &add_data.sm_name, N_("name"),
 			   N_("sets the submodule's name to the given string "
@@ -3700,6 +3725,9 @@ static int module_add(int argc, const char **argv, const char *prefix,
 		if (add_data.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
 			die(_("unknown ref storage format '%s'"), ref_storage_format);
 	}
+	if (add_data.object_storage_format &&
+	    odb_source_type_by_uri(add_data.object_storage_format, NULL) == ODB_SOURCE_UNKNOWN)
+		die(_("unknown object storage format '%s'"), add_data.object_storage_format);
 
 	add_data.repo = argv[0];
 	if (argc == 1)
diff --git a/git-submodule.sh b/git-submodule.sh
index 8632194138..4de5ffba04 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -103,6 +103,14 @@ cmd_add()
 		--ref-format=*|--ref-storage-format=*)
 			ref_storage_format="$1"
 			;;
+		--object-storage-format)
+			case "$2" in '') usage ;; esac
+			object_storage_format="--object-storage-format=$2"
+			shift
+			;;
+		--object-storage-format=*)
+			object_storage_format="$1"
+			;;
 		--dissociate)
 			dissociate=$1
 			;;
@@ -148,6 +156,7 @@ cmd_add()
 		${branch:+"$branch"} \
 		${reference:+"$reference"} \
 		${ref_storage_format:+"$ref_storage_format"} \
+		${object_storage_format:+"$object_storage_format"} \
 		$dissociate \
 		${name:+"$name"} \
 		${depth:+"$depth"} \
@@ -310,6 +319,14 @@ cmd_update()
 		--ref-format=*|--ref-storage-format=*)
 			ref_storage_format="$1"
 			;;
+		--object-storage-format)
+			case "$2" in '') usage ;; esac
+			object_storage_format="--object-storage-format=$2"
+			shift
+			;;
+		--object-storage-format=*)
+			object_storage_format="$1"
+			;;
 		--reference)
 			case "$2" in '') usage ;; esac
 			reference="--reference=$2"
@@ -386,6 +403,7 @@ cmd_update()
 		$merge \
 		$checkout \
 		${ref_storage_format:+"$ref_storage_format"} \
+		${object_storage_format:+"$object_storage_format"} \
 		${reference:+"$reference"} \
 		$dissociate \
 		${depth:+"$depth"} \
diff --git a/t/meson.build b/t/meson.build
index 11b3752f00..8542035b6f 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -926,6 +926,7 @@ integration_tests = [
   't7424-submodule-mixed-ref-formats.sh',
   't7425-submodule-gitdir-path-extension.sh',
   't7426-submodule-get-default-remote.sh',
+  't7427-submodule-object-storage-format.sh',
   't7450-bad-git-dotfiles.sh',
   't7500-commit-template-squash-signoff.sh',
   't7501-commit-basic-functionality.sh',
diff --git a/t/t7427-submodule-object-storage-format.sh b/t/t7427-submodule-object-storage-format.sh
new file mode 100755
index 0000000000..a2c9a5e753
--- /dev/null
+++ b/t/t7427-submodule-object-storage-format.sh
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+test_description='submodules handle the object storage format'
+
+. ./test-lib.sh
+
+test_object_storage_format () {
+	echo "$2" >expect &&
+	git -C "$1" rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'setup' '
+	git config set --global protocol.file.allow always
+'
+
+test_expect_success 'add existing repository with different object storage format' '
+	test_when_finished "rm -rf parent" &&
+
+	git init parent &&
+	(
+		cd parent &&
+		test_commit parent &&
+		git init --object-storage-format=files://objs submodule &&
+		test_commit -C submodule submodule &&
+		git submodule add ./submodule &&
+		test_object_storage_format submodule files://objs
+	)
+'
+
+test_expect_success 'add submodules with different object storage format' '
+	test_when_finished "rm -rf submodule upstream" &&
+
+	git init submodule &&
+	test_commit -C submodule submodule-initial &&
+	git init upstream &&
+	test_object_storage_format upstream files &&
+	git -C upstream submodule add --object-storage-format=files://objs "file://$(pwd)/submodule" &&
+	test_object_storage_format upstream/submodule files://objs &&
+	test_path_is_dir upstream/.git/modules/submodule/objs/pack
+'
+
+test_expect_success 'add submodules with garbage object storage format' '
+	test_when_finished "rm -rf submodule upstream" &&
+
+	git init submodule &&
+	test_commit -C submodule submodule-initial &&
+	git init upstream &&
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format ${SQ}garbage${SQ}
+	EOF
+	test_must_fail git -C upstream submodule add --object-storage-format=garbage "file://$(pwd)/submodule" 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'recursive clone propagates object storage format' '
+	test_when_finished "rm -rf submodule upstream downstream" &&
+
+	git init submodule &&
+	test_commit -C submodule submodule-initial &&
+	git init upstream &&
+	git -C upstream submodule add "file://$(pwd)/submodule" &&
+	git -C upstream commit -am "add submodule" &&
+
+	test_object_storage_format upstream files &&
+	test_object_storage_format upstream/submodule files &&
+
+	git clone --object-storage-format=files://objs --recurse-submodules \
+		upstream downstream &&
+	test_object_storage_format downstream files://objs &&
+	test_object_storage_format downstream/submodule files://objs &&
+	test_path_is_dir downstream/.git/objs/pack &&
+	test_path_is_dir downstream/.git/modules/submodule/objs/pack
+'
+
+test_expect_success 'clone submodules with different object storage format' '
+	test_when_finished "rm -rf submodule upstream downstream" &&
+
+	git init submodule &&
+	test_commit -C submodule submodule-initial &&
+	git init upstream &&
+	git -C upstream submodule add "file://$(pwd)/submodule" &&
+	git -C upstream commit -m "upstream submodule" &&
+
+	git clone --no-recurse-submodules "file://$(pwd)/upstream" downstream &&
+	git -C downstream submodule update --init --object-storage-format=files://objs &&
+	test_object_storage_format downstream files &&
+	test_object_storage_format downstream/submodule files://objs
+'
+
+test_done

-- 
2.56.0.307.g5fe7bd680c.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* Re: [PATCH v2 5/7] builtin/init: introduce handling of object storage format
  2026-10-21  5:39   ` [PATCH v2 5/7] builtin/init: introduce handling of " Patrick Steinhardt
@ 2026-10-21 14:23     ` Karthik Nayak
  2026-10-23  5:33       ` Patrick Steinhardt
  0 siblings, 1 reply; 49+ messages in thread
From: Karthik Nayak @ 2026-10-21 14:23 UTC (permalink / raw)
  To: Patrick Steinhardt, git

[-- Attachment #1: Type: text/plain, Size: 3602 bytes --]

Patrick Steinhardt <ps@pks.im> writes:

> While the object storage format extension can already be configured
> manually via git-config(1), this isn't exactly an ergonomic way of doing
> so. For the reference storage format extension, users have a host of
> options available to them.
>
> Introduce the equivalent options for the object storage format.

[snip]

> diff --git a/setup.c b/setup.c
> index 98140a7641..3d74f8eeed 100644
> --- a/setup.c
> +++ b/setup.c
> @@ -2466,7 +2466,8 @@ void initialize_repository_version(struct repository *repo,
>  	 */
>  	if (hash_algo != GIT_HASH_SHA1_LEGACY ||
>  	    ref_storage_format != REF_STORAGE_FORMAT_FILES ||
> -	    repo->ref_storage_payload)
> +	    repo->ref_storage_payload ||
> +	    strcmp(repo->object_storage_format, "files"))
>  		target_version = GIT_REPO_VERSION_READ;
>

Okay so this is what also sets the 'core.repositoryformatversion'
config. I was specifically looking for that.

Does this mean that even for the files odb with a payload we need v1 of
'core.repositoryformatversion'?

>  	if (hash_algo != GIT_HASH_SHA1_LEGACY && hash_algo != GIT_HASH_UNKNOWN)
> @@ -2490,6 +2491,12 @@ void initialize_repository_version(struct repository *repo,
>  		repo_config_set_gently(repo, "extensions.refstorage", NULL);
>  	}
>
> +	if (strcmp(repo->object_storage_format, "files"))
> +		repo_config_set(repo, "extensions.objectstorage",
> +				repo->object_storage_format);
> +	else if (reinit)
> +		repo_config_set_gently(repo, "extensions.objectstorage", NULL);
> +

So this handled reinitializing the same repo. If there was a previously
set object storage format, we unset it gently.

But, we already catch and error out if the extension before and after
reinit differ, so this `else` block is not needed, no? Removing it
passes all the tests in this patch.

>  	if (reinit) {
>  		struct strbuf config = STRBUF_INIT;
>  		struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
> @@ -2726,6 +2733,7 @@ static void separate_git_dir(struct repository *repo,
>  struct default_format_config {
>  	int hash;
>  	enum ref_storage_format ref_storage_format;
> +	char *object_storage_format;
>  };
>
>  static int read_default_format_config(const char *key, const char *value,

[snip]

> diff --git a/t/t0001-init.sh b/t/t0001-init.sh
> index d080ab04a8..0da14e8f70 100755
> --- a/t/t0001-init.sh
> +++ b/t/t0001-init.sh
> @@ -898,6 +898,168 @@ test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FORMAT overrides ext

[snip]

> +
> +test_expect_success 'init with --object-storage-format=files://<relative-path>' '
> +	test_when_finished "rm -rf objformat" &&
> +	git init --object-storage-format=files://objs objformat &&
> +	echo files://objs >expect &&
> +	git -C objformat config get extensions.objectstorage >actual &&
> +	test_cmp expect actual &&
> +	echo 1 >expect &&
>

Okay what I asked before is expected, files with payload is v1. Makes
sense.

> +	git -C objformat config get core.repositoryformatversion >actual &&
> +	test_cmp expect actual &&
> +	git -C objformat rev-parse --show-object-storage-format >actual &&
> +	echo files://objs >expect &&
> +	test_cmp expect actual &&
> +	test_path_is_dir objformat/.git/objects &&
> +	test_path_is_dir objformat/.git/objs &&
> +	test_commit -C objformat A &&
> +	git -C objformat rev-parse --verify HEAD &&
> +	test_path_is_missing objformat/.git/objects/pack &&
> +	test_path_is_dir objformat/.git/objs/pack

Super nit: some newlines in between would help improve readability of a
few of these denser tests.

> +'
> +

[snip]

Nice. I like the extensive testing done here.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v2 0/7] odb: introduce object storage extension
  2026-10-21  5:39 ` [PATCH v2 0/7] odb: introduce object storage extension Patrick Steinhardt
                     ` (6 preceding siblings ...)
  2026-10-21  5:39   ` [PATCH v2 7/7] builtin/submodule: propagate "--object-storage-format=" Patrick Steinhardt
@ 2026-10-21 14:31   ` Karthik Nayak
  7 siblings, 0 replies; 49+ messages in thread
From: Karthik Nayak @ 2026-10-21 14:31 UTC (permalink / raw)
  To: Patrick Steinhardt, git

[-- Attachment #1: Type: text/plain, Size: 1733 bytes --]

Patrick Steinhardt <ps@pks.im> writes:

> Hi,
>
> this patch series introduces the object storage extension. The extension
> is of course similar in spirit and design to the ref storage extension:
> it can be configured via "extensions.objectStorage", has the same syntax
> with "<schema>[://<payload>]", and we have the same mechanisms to
> configure it ("--object-storage-format=", GIT_OBJECT_STORAGE_FORMAT,
> GIT_DEFAULT_OBJECT_STORAGE_FORMAT, "init.defaultObjetcStorageFormat").
>
> For now, we only support the "files" object storage format, so not a lot
> of stuff really changes. That being said, its optional payload allows
> you to configure a different location for the ".git/objects" directory.
> So if you specify "extensions.objectStorage=files://objs", objects would
> be stored in ".git/objs" instead.
>
> The series is built on top of c51408889c (The 6th batch, 2026-10-16)
> with the following dependencies merged into it:
>
>   - ps/odb-files-alternates at 0ba59c8453 (odb/source: drop
>     `read_alternates` callback, 2026-10-08) merged into it.
>
>   - ps/setup-enforce-repo-passed-to-create-repository-has-no-state
>     (phew, what a mouthful) at 0ba59c8453 (odb/source: drop
>     `read_alternates` callback, 2026-10-08).
>
> Note that these two series conflict with one another. The conflict can
> easily be resolved by just deleting the whole conflicting sections.
>
> Changes in v2:
>   - Ensure that `repo->object_storage_format` is always populated.
>   - Link to v1: https://patch.msgid.link/20261019-b4-pks-odb-extension-v1-0-143a5963b77f@pks.im
>

I had a look at the range diff and it looks as expected, I had one small
cleanup comment in 5/7, but its harmless so I'd be okay with it as is.
Thanks!

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v2 5/7] builtin/init: introduce handling of object storage format
  2026-10-21 14:23     ` Karthik Nayak
@ 2026-10-23  5:33       ` Patrick Steinhardt
  0 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-23  5:33 UTC (permalink / raw)
  To: Karthik Nayak; +Cc: git

On Wed, Oct 21, 2026 at 10:23:17AM -0400, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/setup.c b/setup.c
> > index 98140a7641..3d74f8eeed 100644
> > --- a/setup.c
> > +++ b/setup.c
> > @@ -2466,7 +2466,8 @@ void initialize_repository_version(struct repository *repo,
> >  	 */
> >  	if (hash_algo != GIT_HASH_SHA1_LEGACY ||
> >  	    ref_storage_format != REF_STORAGE_FORMAT_FILES ||
> > -	    repo->ref_storage_payload)
> > +	    repo->ref_storage_payload ||
> > +	    strcmp(repo->object_storage_format, "files"))
> >  		target_version = GIT_REPO_VERSION_READ;
> >
> 
> Okay so this is what also sets the 'core.repositoryformatversion'
> config. I was specifically looking for that.
> 
> Does this mean that even for the files odb with a payload we need v1 of
> 'core.repositoryformatversion'?

Yes. Repository extensions aren't read in the case where we have a v0
repository, so we cannot make use of any non-default object storage
format there.

> > @@ -2490,6 +2491,12 @@ void initialize_repository_version(struct repository *repo,
> >  		repo_config_set_gently(repo, "extensions.refstorage", NULL);
> >  	}
> >
> > +	if (strcmp(repo->object_storage_format, "files"))
> > +		repo_config_set(repo, "extensions.objectstorage",
> > +				repo->object_storage_format);
> > +	else if (reinit)
> > +		repo_config_set_gently(repo, "extensions.objectstorage", NULL);
> > +
> 
> So this handled reinitializing the same repo. If there was a previously
> set object storage format, we unset it gently.
> 
> But, we already catch and error out if the extension before and after
> reinit differ, so this `else` block is not needed, no? Removing it
> passes all the tests in this patch.

This is a slightly different case: if you have a repository that uses
the default storage format, but that explicitly configures the "files"
format, then we downgrade the repository format version to 0 and unset
the extension again. This logic basically matches what we do with the
ref backend, too.

I noticed there's no test for this though. And that then led me down the
rabbit hole, because I'm not particularly happy with some of the logic
here. For example, I was doing normalization of "files" vs "files://"
somewhat inconsistently.

So I've redone some of the infrastructure here to be much closer to what
we do with the ref backend. Currently we track the object storage format
as a single URI that we pass around, whereas for the ref storage format
we pass around a backend type and payload. And that makes normalization
a lot easier, and we don't have to re-parse the URI in a lot of places.

So I've adapted the series now to use the same approach. It also makes
stuff easier to reason about, as we basically have the exact same logic
for both storage types in most places.

I'll send that later today.

Patrick

^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH v3 0/7] odb: introduce object storage extension
  2026-10-19  8:55 [PATCH 0/7] odb: introduce object storage extension Patrick Steinhardt
                   ` (7 preceding siblings ...)
  2026-10-21  5:39 ` [PATCH v2 0/7] odb: introduce object storage extension Patrick Steinhardt
@ 2026-10-23 11:49 ` Patrick Steinhardt
  2026-10-23 11:49   ` [PATCH v3 1/7] loose: drop unused `repo_write_loose_object_map()` Patrick Steinhardt
                     ` (7 more replies)
  8 siblings, 8 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-23 11:49 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

Hi,

this patch series introduces the object storage extension. The extension
is of course similar in spirit and design to the ref storage extension:
it can be configured via "extensions.objectStorage", has the same syntax
with "<schema>[://<payload>]", and we have the same mechanisms to
configure it ("--object-storage-format=", GIT_OBJECT_STORAGE_FORMAT,
GIT_DEFAULT_OBJECT_STORAGE_FORMAT, "init.defaultObjetcStorageFormat").

For now, we only support the "files" object storage format, so not a lot
of stuff really changes. That being said, its optional payload allows
you to configure a different location for the ".git/objects" directory.
So if you specify "extensions.objectStorage=files://objs", objects would
be stored in ".git/objs" instead.

The series is built on top of c51408889c (The 6th batch, 2026-10-16)
with the following dependencies merged into it:

  - ps/odb-files-alternates at 0ba59c8453 (odb/source: drop
    `read_alternates` callback, 2026-10-08) merged into it.

  - ps/setup-enforce-repo-passed-to-create-repository-has-no-state
    (phew, what a mouthful) at 0ba59c8453 (odb/source: drop
    `read_alternates` callback, 2026-10-08).

Note that these two series conflict with one another. The conflict can
easily be resolved by just deleting the whole conflicting sections.

Changes in v3:
  - Split up object storage format URI into object storage format and
    payload. This matches how we do it for the ref storage format and
    thus brings us much closer to it, where we mostly use the exact same
    logic. For now though this restricts a couple cases, like for
    example submodule clones do not pass down payloads to the submodules
    anymore. This is intentional so that object and ref storage format
    have the same behaviour.
  - There's an en explicit `enum object_storage_format` now, matching
    how we handle this for the ref storage format.
  - Link to v2: https://patch.msgid.link/20261021-b4-pks-odb-extension-v2-0-8cf11b65eda8@pks.im

Changes in v2:
  - Ensure that `repo->object_storage_format` is always populated.
  - Link to v1: https://patch.msgid.link/20261019-b4-pks-odb-extension-v1-0-143a5963b77f@pks.im

Thanks!

Patrick

---
Patrick Steinhardt (7):
      loose: drop unused `repo_write_loose_object_map()`
      setup: introduce object storage extension
      setup: allow overriding object storage format via environment
      builtin: expose the object storage format
      builtin/init: introduce handling of object storage format
      builtin/clone: introduce "--object-storage-format="
      builtin/submodule: propagate "--object-storage-format="

 Documentation/config/extensions.adoc       |  23 +++
 Documentation/config/init.adoc             |   5 +
 Documentation/git-clone.adoc               |  11 ++
 Documentation/git-init.adoc                |  10 +-
 Documentation/git-repo.adoc                |   6 +
 Documentation/git-rev-parse.adoc           |   5 +
 Documentation/git-submodule.adoc           |  10 +-
 Documentation/git.adoc                     |  10 ++
 Documentation/object-storage-format.adoc   |   6 +
 builtin/clone.c                            |  63 +++++++-
 builtin/init-db.c                          |   7 +-
 builtin/repo.c                             |  10 ++
 builtin/rev-parse.c                        |   9 ++
 builtin/submodule--helper.c                |  40 +++++
 environment.h                              |   1 +
 git-submodule.sh                           |  18 +++
 loose.c                                    |  46 ------
 loose.h                                    |   1 -
 odb.c                                      |  46 +++++-
 odb.h                                      |  13 ++
 odb/source-files.c                         |   7 +
 odb/source-files.h                         |   1 +
 odb/source.c                               |  14 +-
 odb/source.h                               |   5 +-
 refs.c                                     |   3 +-
 repository.c                               |  10 ++
 repository.h                               |  18 +++
 setup.c                                    | 133 ++++++++++++++++-
 setup.h                                    |   5 +
 t/meson.build                              |   1 +
 t/t0001-init.sh                            | 225 +++++++++++++++++++++++++++++
 t/t1900-repo-info.sh                       |   3 +
 t/t5601-clone.sh                           |  68 +++++++++
 t/t7427-submodule-object-storage-format.sh | 104 +++++++++++++
 t/unit-tests/u-odb-inmemory.c              |   1 +
 35 files changed, 872 insertions(+), 66 deletions(-)

Range-diff versus v2:

1:  04226610d7 = 1:  deda9d5530 loose: drop unused `repo_write_loose_object_map()`
2:  34afb43448 ! 2:  f6a73a7934 setup: introduce object storage extension
    @@ Documentation/object-storage-format.adoc (new)
     +provided. The payload is ignored when `GIT_OBJECT_DIRECTORY` is set.
     
      ## odb.c ##
    +@@
    + #include "trace2.h"
    + #include "write-or-die.h"
    + 
    ++static const char * const object_storage_name_by_format[] = {
    ++	[OBJECT_STORAGE_FORMAT_UNKNOWN] = "unknown",
    ++	[OBJECT_STORAGE_FORMAT_FILES] = "files",
    ++};
    ++
    ++enum object_storage_format object_storage_format_by_name(const char *name)
    ++{
    ++	for (size_t i = 0; i < ARRAY_SIZE(object_storage_name_by_format); i++)
    ++		if (!strcmp(object_storage_name_by_format[i], name))
    ++			return i;
    ++	return OBJECT_STORAGE_FORMAT_UNKNOWN;
    ++}
    ++
    ++enum object_storage_format object_storage_format_by_uri(const char *uri, char **payload)
    ++{
    ++	enum object_storage_format format;
    ++	const char *schema_end;
    ++	char *name;
    ++
    ++	schema_end = strstr(uri, "://");
    ++	if (!schema_end) {
    ++		name = xstrdup(uri);
    ++		if (payload)
    ++			*payload = NULL;
    ++	} else {
    ++		name = xstrndup(uri, schema_end - uri);
    ++		if (payload)
    ++			*payload = *(schema_end + 3) ? xstrdup(schema_end + 3) : NULL;
    ++	}
    ++
    ++	format = object_storage_format_by_name(name);
    ++
    ++	free(name);
    ++	return format;
    ++}
    ++
    + int odb_mkstemp(struct object_database *odb,
    + 		struct strbuf *temp_filename, const char *pattern)
    + {
     @@ odb.c: struct object_database *odb_new(struct repository *repo,
      	o->repo = repo;
      	pthread_mutex_init(&o->replace_mutex, NULL);
      
     -	o->source = odb_source_new(o, flags);
    -+	o->source = odb_source_new(o, repo->object_storage_format, flags);
    ++	o->source = odb_source_new(o, repo->object_storage_format,
    ++				   repo->object_storage_payload, flags);
      	o->inmemory_objects = &odb_source_inmemory_new(o)->base;
      
      	return o;
     
    + ## odb.h ##
    +@@
    + #include "oid-array.h"
    + #include "oidset.h"
    + #include "oidmap.h"
    ++#include "repository.h"
    + #include "string-list.h"
    + #include "thread-utils.h"
    + 
    +@@ odb.h: struct strvec;
    +  */
    + char *compute_alternate_path(const char *path, struct strbuf *err);
    + 
    ++/*
    ++ * Parse the given URI in the format "<schema>[://<payload>]" and return the
    ++ * object storage format identified by the schema. If `payload` is non-NULL it
    ++ * will be set to a newly allocated copy of the payload, or NULL if the URI has
    ++ * no or an empty payload. Returns `OBJECT_STORAGE_FORMAT_UNKNOWN` for unknown
    ++ * schemas.
    ++ */
    ++enum object_storage_format object_storage_format_by_uri(const char *uri, char **payload);
    ++
    ++enum object_storage_format object_storage_format_by_name(const char *name);
    ++
    + /*
    +  * The object database encapsulates access to objects in a repository. It
    +  * manages the object source as well as auxiliary data structures required to
    +
      ## odb/source-files.c ##
     @@ odb/source-files.c: struct odb_files_dir *odb_source_files_find_dir(struct object_database *odb, con
      }
    @@ odb/source-files.h: struct odb_source_files {
      /*
     
      ## odb/source.c ##
    -@@
    - #include "git-compat-util.h"
    -+#include "gettext.h"
    - #include "object-file.h"
    - #include "odb/source-files.h"
    - #include "odb/source.h"
     @@ odb/source.c: const char *odb_source_type_to_name(enum odb_source_type type)
    - 	return name;
      }
      
    -+static enum odb_source_type odb_source_type_by_name(const char *name)
    -+{
    -+	if (!strcmp(name, "files"))
    -+		return ODB_SOURCE_FILES;
    -+	return ODB_SOURCE_UNKNOWN;
    -+}
    -+
    -+enum odb_source_type odb_source_type_by_uri(const char *uri, char **payload)
    -+{
    -+	enum odb_source_type type;
    -+	const char *schema_end;
    -+	char *name;
    -+
    -+	schema_end = strstr(uri, "://");
    -+	if (!schema_end) {
    -+		name = xstrdup(uri);
    -+		if (payload)
    -+			*payload = NULL;
    -+	} else {
    -+		name = xstrndup(uri, schema_end - uri);
    -+		if (payload)
    -+			*payload = *(schema_end + 3) ? xstrdup(schema_end + 3) : NULL;
    -+	}
    -+
    -+	type = odb_source_type_by_name(name);
    -+	free(name);
    -+	return type;
    -+}
    -+
      struct odb_source *odb_source_new(struct object_database *odb,
    -+				  const char *object_storage_format,
    ++				  enum object_storage_format format,
    ++				  const char *payload,
      				  enum odb_new_flags flags)
      {
     -	return &odb_source_files_new(odb, flags)->base;
     +	struct odb_source *source;
    -+	char *payload = NULL;
     +
    -+	switch (odb_source_type_by_uri(object_storage_format, &payload)) {
    -+	case ODB_SOURCE_FILES:
    ++	switch (format) {
    ++	case OBJECT_STORAGE_FORMAT_FILES:
     +		source = &odb_source_files_new(odb, payload, flags)->base;
     +		break;
     +	default:
    -+		die(_("unknown object storage format '%s'"), object_storage_format);
    ++		BUG("unknown object storage format");
     +	}
     +
    -+	free(payload);
     +	return source;
      }
      
      void odb_source_init(struct odb_source *source,
     
      ## odb/source.h ##
    -@@ odb/source.h: enum odb_source_type {
    -  */
    - const char *odb_source_type_to_name(enum odb_source_type type);
    - 
    -+/*
    -+ * Parse the given URI in the format "<schema>[://<payload>]" and return the
    -+ * source type identified by the schema. If `payload` is non-NULL it will be
    -+ * set to a newly allocated copy of the payload, or NULL if the URI has no or
    -+ * an empty payload. Returns `ODB_SOURCE_UNKNOWN` for unknown schemas.
    -+ */
    -+enum odb_source_type odb_source_type_by_uri(const char *uri, char **payload);
    -+
    - struct object_id;
    - struct odb_stream;
    - struct strbuf;
     @@ odb/source.h: struct odb_source {
      
      /*
    @@ odb/source.h: struct odb_source {
     + * format.
       */
      struct odb_source *odb_source_new(struct object_database *odb,
    -+				  const char *object_storage_format,
    ++				  enum object_storage_format format,
    ++				  const char *payload,
      				  enum odb_new_flags flags);
      
      /*
    @@ repository.c: void repo_set_ref_storage_format(struct repository *repo,
      }
      
     +void repo_set_object_storage_format(struct repository *repo,
    -+				    const char *uri)
    ++				    enum object_storage_format format,
    ++				    const char *payload)
     +{
    -+	free(repo->object_storage_format);
    -+	if (uri)
    -+		repo->object_storage_format = xstrdup(uri);
    -+	else
    -+		repo->object_storage_format = xstrdup("files");
    ++	repo->object_storage_format = format;
    ++	free(repo->object_storage_payload);
    ++	repo->object_storage_payload = xstrdup_or_null(payload);
     +}
     +
      /*
    @@ repository.c: void repo_clear(struct repository *repo)
      	free(repo->worktree);
      	free(repo->submodule_prefix);
      	free(repo->ref_storage_payload);
    -+	free(repo->object_storage_format);
    ++	free(repo->object_storage_payload);
      
      	odb_free(repo->objects);
      
     
      ## repository.h ##
    +@@ repository.h: enum ref_storage_format {
    + 	REF_STORAGE_FORMAT_REFTABLE,
    + };
    + 
    ++enum object_storage_format {
    ++	OBJECT_STORAGE_FORMAT_UNKNOWN,
    ++	OBJECT_STORAGE_FORMAT_FILES,
    ++};
    ++
    + #ifdef WITH_BREAKING_CHANGES /* Git 3.0 */
    + # define REF_STORAGE_FORMAT_DEFAULT REF_STORAGE_FORMAT_REFTABLE
    + #else
     @@ repository.h: struct repository {
      	 */
      	char *ref_storage_payload;
      
    ++	/* Repository's object storage format, as serialized on disk. */
    ++	enum object_storage_format object_storage_format;
    ++
     +	/*
    -+	 * The object storage URI in the format "<schema>[://<payload>]", as
    -+	 * configured via "extensions.objectStorage".
    ++	 * Object storage information as needed for the backend. This contains
    ++	 * only the payload from the object storage format URI without the
    ++	 * schema.
     +	 */
    -+	char *object_storage_format;
    ++	char *object_storage_payload;
     +
      	/* A unique-id for tracing purposes. */
      	int trace2_repo_id;
    @@ repository.h: void repo_set_compat_hash_algo(struct repository *repo, uint32_t c
      				 enum ref_storage_format format,
      				 const char *payload);
     +void repo_set_object_storage_format(struct repository *repo,
    -+				    const char *uri);
    ++				    enum object_storage_format format,
    ++				    const char *payload);
      void initialize_repository(struct repository *repo);
      RESULT_MUST_BE_USED
      int repo_init(struct repository *r, const char *gitdir, const char *worktree);
     
      ## setup.c ##
    -@@
    - #include "hex.h"
    - #include "object-file.h"
    - #include "object-name.h"
    -+#include "odb/source.h"
    - #include "refs.h"
    - #include "repository.h"
    - #include "config.h"
     @@ setup.c: static enum extension_result handle_extension(const char *var,
    + 			return error(_("invalid value for '%s': '%s'"),
      				     "extensions.refstorage", value);
      		data->ref_storage_format = format;
    - 		return EXTENSION_OK;
    ++		return EXTENSION_OK;
     +	} else if (!strcmp(ext, "objectstorage")) {
    ++		enum object_storage_format format;
    ++
     +		if (!value)
     +			return config_error_nonbool(var);
    -+		if (odb_source_type_by_uri(value, NULL) == ODB_SOURCE_UNKNOWN)
    ++
    ++		FREE_AND_NULL(data->object_storage_payload);
    ++		format = object_storage_format_by_uri(value, &data->object_storage_payload);
    ++		if (format == OBJECT_STORAGE_FORMAT_UNKNOWN)
     +			return error(_("invalid value for '%s': '%s'"),
     +				     "extensions.objectstorage", value);
    -+		free(data->object_storage);
    -+		data->object_storage = xstrdup(value);
    -+		return EXTENSION_OK;
    ++		data->object_storage_format = format;
    ++
    + 		return EXTENSION_OK;
      	} else if (!strcmp(ext, "relativeworktrees")) {
      		data->relative_worktrees = git_config_bool(var, value);
    - 		return EXTENSION_OK;
     @@ setup.c: static enum extension_result handle_extension(const char *var,
      		data->submodule_path_cfg = git_config_bool(var, value);
      		return EXTENSION_OK;
    @@ setup.c: void clear_repository_format(struct repository_format *format)
      	free(format->work_tree);
      	free(format->partial_clone);
      	free(format->ref_storage_payload);
    -+	free(format->object_storage);
    ++	free(format->object_storage_payload);
      	init_repository_format(format);
      }
      
    @@ setup.c: int apply_repository_format(struct repository *repo,
      	repo_set_ref_storage_format(repo,
      				    format->ref_storage_format,
      				    format->ref_storage_payload);
    -+	repo_set_object_storage_format(repo, format->object_storage);
    ++	repo_set_object_storage_format(repo,
    ++				       format->object_storage_format,
    ++				       format->object_storage_payload);
      	repo->repository_format_worktree_config =
      		format->worktree_config;
      	repo->repository_format_submodule_path_cfg =
     
      ## setup.h ##
     @@ setup.h: struct repository_format {
    + 	int compat_hash_algo;
    + 	enum ref_storage_format ref_storage_format;
      	char *ref_storage_payload;
    ++	enum object_storage_format object_storage_format;
    ++	char *object_storage_payload;
      	int sparse_index;
      	char *work_tree;
    -+	char *object_storage;
      	struct string_list unknown_extensions;
    - 	struct string_list v1_only_extensions;
    - };
    +@@ setup.h: struct repository_format {
    + 	.is_bare = -1, \
    + 	.hash_algo = GIT_HASH_DEFAULT, \
    + 	.ref_storage_format = REF_STORAGE_FORMAT_FILES, \
    ++	.object_storage_format = OBJECT_STORAGE_FORMAT_FILES, \
    + 	.unknown_extensions = STRING_LIST_INIT_DUP, \
    + 	.v1_only_extensions = STRING_LIST_INIT_DUP, \
    + }
     
      ## t/t0001-init.sh ##
     @@ t/t0001-init.sh: test_expect_success 'init with --ref-storage-format=garbage' '
    @@ t/unit-tests/u-odb-inmemory.c
      
      static struct repository repo = {
      	.hash_algo = &hash_algos[GIT_HASH_SHA1],
    -+	.object_storage_format = (char *) "files",
    ++	.object_storage_format = OBJECT_STORAGE_FORMAT_FILES,
      };
      static struct object_database *odb;
      
3:  05c6ce2866 ! 3:  54090c50c3 setup: allow overriding object storage format via environment
    @@ setup.c: const char *setup_git_directory_gently(struct repository *repo, int *no
     +			 */
     +			object_storage_uri = getenv(GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT);
     +			if (object_storage_uri) {
    -+				if (odb_source_type_by_uri(object_storage_uri, NULL) == ODB_SOURCE_UNKNOWN)
    ++				FREE_AND_NULL(discovery.format.object_storage_payload);
    ++				discovery.format.object_storage_format =
    ++					object_storage_format_by_uri(object_storage_uri,
    ++								     &discovery.format.object_storage_payload);
    ++				if (discovery.format.object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
     +					die(_("unknown object storage format: '%s'"), object_storage_uri);
    -+				free(discovery.format.object_storage);
    -+				discovery.format.object_storage = xstrdup(object_storage_uri);
     +			}
     +
      			if (apply_repository_format(repo, &discovery.format,
4:  487e927a5c ! 4:  f6b57aa2fe builtin: expose the object storage format
    @@ Documentation/git-rev-parse.adoc: The following options are unaffected by `--pat
      ~~~~~~~~~~~~~
     
      ## builtin/repo.c ##
    -@@
    - #include "hash.h"
    - #include "hex.h"
    - #include "odb.h"
    -+#include "odb/source.h"
    - #include "parse-options.h"
    - #include "path.h"
    - #include "path-walk.h"
     @@ builtin/repo.c: static int get_references_format(struct repository *repo, struct strbuf *buf)
      	return 0;
      }
      
     +static int get_object_storage_format(struct repository *repo, struct strbuf *buf)
     +{
    -+	strbuf_addstr(buf, repo->object_storage_format);
    ++	strbuf_addstr(buf,
    ++		      object_storage_format_to_name(repo->object_storage_format));
    ++	if (repo->object_storage_payload)
    ++		strbuf_addf(buf, "://%s", repo->object_storage_payload);
     +	return 0;
     +}
     +
    @@ builtin/rev-parse.c
      #include "gettext.h"
      #include "hash.h"
      #include "hex.h"
    -+#include "odb/source.h"
    ++#include "odb.h"
      #include "refs.h"
      #include "quote.h"
      #include "object-name.h"
    @@ builtin/rev-parse.c: int cmd_rev_parse(int argc,
      				continue;
      			}
     +			if (!strcmp(arg, "--show-object-storage-format")) {
    -+				puts(the_repository->object_storage_format);
    ++				const char *format = object_storage_format_to_name(the_repository->object_storage_format);
    ++				if (the_repository->object_storage_payload)
    ++					printf("%s://%s\n", format, the_repository->object_storage_payload);
    ++				else
    ++					puts(format);
     +				continue;
     +			}
      			if (!strcmp(arg, "--end-of-options")) {
      				seen_end_of_options = 1;
      				if (filter & (DO_FLAGS | DO_REVS))
     
    + ## odb.c ##
    +@@ odb.c: enum object_storage_format object_storage_format_by_name(const char *name)
    + 	return OBJECT_STORAGE_FORMAT_UNKNOWN;
    + }
    + 
    ++const char *object_storage_format_to_name(enum object_storage_format format)
    ++{
    ++	if (format < 0 || format >= ARRAY_SIZE(object_storage_name_by_format))
    ++		format = OBJECT_STORAGE_FORMAT_UNKNOWN;
    ++	return object_storage_name_by_format[format];
    ++}
    ++
    + enum object_storage_format object_storage_format_by_uri(const char *uri, char **payload)
    + {
    + 	enum object_storage_format format;
    +
    + ## odb.h ##
    +@@ odb.h: char *compute_alternate_path(const char *path, struct strbuf *err);
    + enum object_storage_format object_storage_format_by_uri(const char *uri, char **payload);
    + 
    + enum object_storage_format object_storage_format_by_name(const char *name);
    ++const char *object_storage_format_to_name(enum object_storage_format format);
    + 
    + /*
    +  * The object database encapsulates access to objects in a repository. It
    +
      ## t/t0001-init.sh ##
     @@ t/t0001-init.sh: test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with files bac
      	git -C objstorage config set core.repositoryformatversion 1 &&
5:  096441cb3f ! 5:  6f1ac304eb builtin/init: introduce handling of object storage format
    @@ builtin/clone.c: int cmd_clone(int argc,
      
      	if (real_git_dir) {
      		free((char *)git_dir);
    +@@ builtin/clone.c: int cmd_clone(int argc,
    + 	 * This is sufficient for Git commands to discover the Git directory.
    + 	 */
    + 	initialize_repository_version(the_repository, GIT_HASH_UNKNOWN,
    +-				      the_repository->ref_storage_format, 1);
    ++				      the_repository->ref_storage_format,
    ++				      the_repository->object_storage_format, 1);
    + 
    + 	refs_create_refdir_stubs(the_repository, git_dir, NULL);
    + 
    +@@ builtin/clone.c: int cmd_clone(int argc,
    + 	 * ours to the same thing.
    + 	 */
    + 	hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));
    +-	initialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);
    ++	initialize_repository_version(the_repository, hash_algo,
    ++				      the_repository->ref_storage_format,
    ++				      the_repository->object_storage_format, 1);
    + 	repo_set_hash_algo(the_repository, hash_algo);
    + 	create_reference_database(the_repository, NULL, 1);
    + 	startup_info->have_repository = 1;
     
      ## builtin/init-db.c ##
     @@ builtin/init-db.c: static int shared_callback(const struct option *opt, const char *arg, int unset)
    @@ builtin/init-db.c: int cmd_init_db(int argc,
      	create_object_database(the_repository, NULL);
      
     
    + ## refs.c ##
    +@@ refs.c: int repo_migrate_ref_storage_format(struct repository *repo,
    + 	 * repository format so that clients will use the new ref store.
    + 	 * We also need to swap out the repository's main ref store.
    + 	 */
    +-	initialize_repository_version(repo, hash_algo_by_ptr(repo->hash_algo), format, 1);
    ++	initialize_repository_version(repo, hash_algo_by_ptr(repo->hash_algo), format,
    ++				      repo->object_storage_format, 1);
    + 
    + 	/*
    + 	 * Unset the old ref store and release it. `get_main_ref_store()` will
    +
      ## setup.c ##
    +@@ setup.c: static int needs_work_tree_config(const char *git_dir, const char *work_tree)
    + void initialize_repository_version(struct repository *repo,
    + 				   int hash_algo,
    + 				   enum ref_storage_format ref_storage_format,
    ++				   enum object_storage_format object_storage_format,
    + 				   int reinit)
    + {
    + 	struct strbuf repo_version = STRBUF_INIT;
     @@ setup.c: void initialize_repository_version(struct repository *repo,
      	 */
      	if (hash_algo != GIT_HASH_SHA1_LEGACY ||
      	    ref_storage_format != REF_STORAGE_FORMAT_FILES ||
     -	    repo->ref_storage_payload)
     +	    repo->ref_storage_payload ||
    -+	    strcmp(repo->object_storage_format, "files"))
    ++	    object_storage_format != OBJECT_STORAGE_FORMAT_FILES ||
    ++	    repo->object_storage_payload)
      		target_version = GIT_REPO_VERSION_READ;
      
      	if (hash_algo != GIT_HASH_SHA1_LEGACY && hash_algo != GIT_HASH_UNKNOWN)
    @@ setup.c: void initialize_repository_version(struct repository *repo,
      		repo_config_set_gently(repo, "extensions.refstorage", NULL);
      	}
      
    -+	if (strcmp(repo->object_storage_format, "files"))
    ++	if (repo->object_storage_payload) {
    ++		struct strbuf uri = STRBUF_INIT;
    ++
    ++		strbuf_addf(&uri, "%s://%s",
    ++			    object_storage_format_to_name(object_storage_format),
    ++			    repo->object_storage_payload);
    ++		repo_config_set(repo, "extensions.objectstorage", uri.buf);
    ++
    ++		strbuf_release(&uri);
    ++	} else if (object_storage_format != OBJECT_STORAGE_FORMAT_FILES) {
     +		repo_config_set(repo, "extensions.objectstorage",
    -+				repo->object_storage_format);
    -+	else if (reinit)
    ++				object_storage_format_to_name(object_storage_format));
    ++	} else if (reinit) {
     +		repo_config_set_gently(repo, "extensions.objectstorage", NULL);
    ++	}
     +
      	if (reinit) {
      		struct strbuf config = STRBUF_INIT;
      		struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
    +@@ setup.c: static int create_default_files(struct repository *repo,
    + 		adjust_shared_perm(repo, repo_get_git_dir(repo));
    + 	}
    + 
    +-	initialize_repository_version(repo, fmt->hash_algo, fmt->ref_storage_format, reinit);
    ++	initialize_repository_version(repo, fmt->hash_algo, fmt->ref_storage_format,
    ++				      fmt->object_storage_format, reinit);
    + 
    + 	/* Check filemode trustability */
    + 	repo_git_path_replace(repo, &path, "config");
     @@ setup.c: static void separate_git_dir(struct repository *repo,
      struct default_format_config {
      	int hash;
      	enum ref_storage_format ref_storage_format;
    -+	char *object_storage_format;
    ++	enum object_storage_format object_storage_format;
      };
      
      static int read_default_format_config(const char *key, const char *value,
    @@ setup.c: static int read_default_format_config(const char *key, const char *valu
     +		ret = git_config_string(&str, key, value);
     +		if (ret)
     +			goto out;
    -+		if (odb_source_type_by_uri(str, NULL) == ODB_SOURCE_UNKNOWN) {
    ++		cfg->object_storage_format = object_storage_format_by_name(str);
    ++		if (cfg->object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
     +			warning(_("unknown object storage format '%s'"), str);
    -+			goto out;
    -+		}
    -+		free(cfg->object_storage_format);
    -+		cfg->object_storage_format = str;
    -+		str = NULL;
     +		goto out;
     +	}
     +
    @@ setup.c: static int read_default_format_config(const char *key, const char *valu
      	struct default_format_config cfg = {
      		.hash = GIT_HASH_UNKNOWN,
     @@ setup.c: static void repository_format_configure(struct repository_format *repo_fmt,
    + 		.ignore_repo = 1,
    + 		.ignore_worktree = 1,
      	};
    ++	enum object_storage_format object_storage_format;
      	enum ref_storage_format ref_storage_format;
    ++	char *object_storage_payload = NULL;
      	char *ref_storage_payload = NULL;
    -+	char *object_storage = NULL;
      	const char *env;
      
    - 	config_with_options(read_default_format_config, &cfg, NULL, NULL, &opts);
     @@ setup.c: static void repository_format_configure(struct repository_format *repo_fmt,
      		ref_storage_format = REF_STORAGE_FORMAT_DEFAULT;
      	}
    @@ setup.c: static void repository_format_configure(struct repository_format *repo_
     +	 *   6. The "files" format.
     +	 */
     +	if (object_storage_uri) {
    -+		if (odb_source_type_by_uri(object_storage_uri, NULL) == ODB_SOURCE_UNKNOWN)
    -+			die(_("unknown object storage format '%s'"), object_storage_uri);
    -+		object_storage = xstrdup(object_storage_uri);
    ++		object_storage_format = object_storage_format_by_uri(object_storage_uri,
    ++								     &object_storage_payload);
    ++		if (object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
    ++			die(_("unknown object storage format specified via command line: '%s'"),
    ++			    object_storage_uri);
     +	} else if ((env = getenv(GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT))) {
    -+		if (odb_source_type_by_uri(env, NULL) == ODB_SOURCE_UNKNOWN)
    ++		object_storage_format = object_storage_format_by_uri(env, &object_storage_payload);
    ++		if (object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
     +			die(_("unknown object storage format specified via %s: '%s'"),
     +			    GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT, env);
    -+		object_storage = xstrdup(env);
     +	} else if (repo_fmt->version >= 0) {
    -+		object_storage = xstrdup_or_null(repo_fmt->object_storage);
    ++		object_storage_format = repo_fmt->object_storage_format;
    ++		object_storage_payload = xstrdup_or_null(repo_fmt->object_storage_payload);
     +	} else if ((env = getenv("GIT_DEFAULT_OBJECT_STORAGE_FORMAT"))) {
    -+		if (odb_source_type_by_uri(env, NULL) == ODB_SOURCE_UNKNOWN)
    ++		object_storage_format = object_storage_format_by_name(env);
    ++		if (object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
     +			die(_("unknown object storage format specified via %s: '%s'"),
     +			    "GIT_DEFAULT_OBJECT_STORAGE_FORMAT", env);
    -+		object_storage = xstrdup(env);
    ++	} else if (cfg.object_storage_format != OBJECT_STORAGE_FORMAT_UNKNOWN) {
    ++		object_storage_format = cfg.object_storage_format;
     +	} else {
    -+		object_storage = xstrdup_or_null(cfg.object_storage_format);
    -+	}
    -+
    -+	/*
    -+	 * The "files" format without a payload is the default and thus doesn't
    -+	 * need to be recorded as an extension.
    -+	 */
    -+	if (object_storage) {
    -+		char *payload = NULL;
    -+		if (odb_source_type_by_uri(object_storage, &payload) == ODB_SOURCE_FILES && !payload)
    -+			FREE_AND_NULL(object_storage);
    -+		free(payload);
    ++		object_storage_format = OBJECT_STORAGE_FORMAT_FILES;
     +	}
     +
      	/*
    @@ setup.c: static void repository_format_configure(struct repository_format *repo_
      		    strcmp(ref_storage_payload ? ref_storage_payload : "",
      			   repo_fmt->ref_storage_payload ? repo_fmt->ref_storage_payload : ""))
      			die(_("attempt to reinitialize repository with different reference storage payload"));
    -+		if (strcmp(object_storage ? object_storage : "",
    -+			   repo_fmt->object_storage ? repo_fmt->object_storage : ""))
    ++		if (object_storage_format != repo_fmt->object_storage_format)
     +			die(_("attempt to reinitialize repository with different object storage format"));
    ++		if ((object_storage_payload || repo_fmt->object_storage_payload) &&
    ++		    strcmp(object_storage_payload ? object_storage_payload : "",
    ++			   repo_fmt->object_storage_payload ? repo_fmt->object_storage_payload : ""))
    ++			die(_("attempt to reinitialize repository with different object storage payload"));
      	}
      
      	free(repo_fmt->ref_storage_payload);
      	repo_fmt->ref_storage_format = ref_storage_format;
      	repo_fmt->ref_storage_payload = ref_storage_payload;
     +
    -+	free(repo_fmt->object_storage);
    -+	repo_fmt->object_storage = object_storage;
    -+	free(cfg.object_storage_format);
    ++	free(repo_fmt->object_storage_payload);
    ++	repo_fmt->object_storage_format = object_storage_format;
    ++	repo_fmt->object_storage_payload = object_storage_payload;
      }
      
      void create_repository(struct repository *repo,
    @@ setup.h: void create_repository(struct repository *repo,
      		       int init_shared_repository,
      		       int *reinit_ok);
      
    + void initialize_repository_version(struct repository *repo,
    + 				   int hash_algo,
    + 				   enum ref_storage_format ref_storage_format,
    ++				   enum object_storage_format object_storage_format,
    + 				   int reinit);
    + 
    + /*
     
      ## t/t0001-init.sh ##
     @@ t/t0001-init.sh: test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FORMAT overrides ext
    @@ t/t0001-init.sh: test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FOR
     +test_expect_success 'init with --object-storage-format=garbage' '
     +	test_when_finished "rm -rf objformat" &&
     +	cat >expect <<-EOF &&
    -+	fatal: unknown object storage format ${SQ}garbage${SQ}
    ++	fatal: unknown object storage format specified via command line: ${SQ}garbage${SQ}
     +	EOF
     +	test_must_fail git init --object-storage-format=garbage objformat 2>err &&
     +	test_cmp expect err
    @@ t/t0001-init.sh: test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FOR
     +	test_cmp expect err
     +'
     +
    -+test_expect_success 'init with GIT_DEFAULT_OBJECT_STORAGE_FORMAT=files://objs' '
    -+	test_when_finished "rm -rf objformat" &&
    -+	GIT_DEFAULT_OBJECT_STORAGE_FORMAT=files://objs git init objformat &&
    -+	echo files://objs >expect &&
    -+	git -C objformat rev-parse --show-object-storage-format >actual &&
    -+	test_cmp expect actual
    -+'
    -+
     +test_expect_success 'init with GIT_OBJECT_STORAGE_FORMAT=garbage' '
     +	test_when_finished "rm -rf objformat" &&
     +	cat >expect <<-EOF &&
    @@ t/t0001-init.sh: test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FOR
     +	test_when_finished "rm -rf objformat" &&
     +	git init --object-storage-format=files objformat &&
     +	cat >expect <<-EOF &&
    -+	fatal: attempt to reinitialize repository with different object storage format
    ++	fatal: attempt to reinitialize repository with different object storage payload
     +	EOF
     +	test_must_fail env GIT_OBJECT_STORAGE_FORMAT=files://objs git init objformat 2>err &&
     +	test_cmp expect err
    @@ t/t0001-init.sh: test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FOR
     +	test_cmp expect actual
     +'
     +
    ++test_expect_success DEFAULT_REPO_FORMAT 're-init with default object storage format drops extension' '
    ++	test_when_finished "rm -rf objformat" &&
    ++
    ++	git init objformat &&
    ++	git -C objformat config set core.repositoryformatversion 1 &&
    ++	git -C objformat config set extensions.objectstorage files &&
    ++
    ++	git init --object-storage-format=files objformat &&
    ++	echo 0 >expect &&
    ++	git -C objformat config get core.repositoryformatversion >actual &&
    ++	test_cmp expect actual &&
    ++	test_must_fail git -C objformat config get extensions.objectstorage
    ++'
    ++
     +test_expect_success 'init warns about invalid init.defaultObjectStorageFormat' '
     +	test_when_finished "rm -rf objformat" &&
     +	test_config_global init.defaultObjectStorageFormat garbage &&
    @@ t/t0001-init.sh: test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FOR
     +
     +test_expect_success 'init with init.defaultObjectStorageFormat' '
     +	test_when_finished "rm -rf objformat" &&
    -+	test_config_global init.defaultObjectStorageFormat files://objs &&
    ++	test_config_global init.defaultObjectStorageFormat files &&
     +	git init objformat &&
    -+	echo files://objs >expect &&
    ++	echo files >expect &&
     +	git -C objformat rev-parse --show-object-storage-format >actual &&
    -+	test_cmp expect actual &&
    -+	test_commit -C objformat A &&
    -+	test_path_is_dir objformat/.git/objs/pack
    ++	test_cmp expect actual
     +'
     +
      test_expect_success MINGW 'core.hidedotfiles = false' '
6:  77586494fa ! 6:  35b38d75e8 builtin/clone: introduce "--object-storage-format="
    @@ Documentation/git-clone.adoc: include::ref-storage-format.adoc[]
      	The number of submodules fetched at the same time.
     
      ## builtin/clone.c ##
    -@@
    - #include "refspec.h"
    - #include "object-file.h"
    - #include "odb.h"
    -+#include "odb/source.h"
    - #include "tree.h"
    - #include "tree-walk.h"
    - #include "unpack-trees.h"
     @@ builtin/clone.c: static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
      	dir_iterator_free(iter);
      }
    @@ builtin/clone.c: static void copy_or_link_directory(struct strbuf *src, struct s
     +{
     +	struct repository_format format = REPOSITORY_FORMAT_INIT;
     +	struct strbuf config = STRBUF_INIT;
    -+	bool uses_default_format = true;
    -+	char *payload = NULL;
    ++	bool is_default;
     +
     +	get_common_dir(&config, repo_path);
     +	strbuf_addstr(&config, "/config");
     +	read_repository_format(&format, config.buf);
     +
    -+	if (format.version >= 1 &&
    -+	    format.object_storage &&
    -+	    (odb_source_type_by_uri(format.object_storage, &payload) != ODB_SOURCE_FILES || payload))
    -+		uses_default_format = false;
    ++	is_default = format.object_storage_format == OBJECT_STORAGE_FORMAT_FILES &&
    ++		!format.object_storage_payload;
     +
     +	clear_repository_format(&format);
     +	strbuf_release(&config);
    -+	free(payload);
    -+	return uses_default_format;
    ++	return is_default;
     +}
     +
      static void clone_local(const char *src_repo, const char *dest_repo)
    @@ builtin/clone.c: int cmd_clone(int argc,
      	}
      
     +	if (object_storage_format_uri &&
    -+	    odb_source_type_by_uri(object_storage_format_uri, NULL) == ODB_SOURCE_UNKNOWN)
    ++	    object_storage_format_by_uri(object_storage_format_uri, NULL) == OBJECT_STORAGE_FORMAT_UNKNOWN)
     +		die(_("unknown object storage format '%s'"), object_storage_format_uri);
     +
      	if (option_mirror) {
7:  9071a404ef ! 7:  3b5c33b7ad builtin/submodule: propagate "--object-storage-format="
    @@ builtin/clone.c: static int git_sparse_checkout_init(const char *repo)
      		    int filter_submodules,
     -		    enum ref_storage_format ref_storage_format)
     +		    enum ref_storage_format ref_storage_format,
    -+		    const char *object_storage_format_uri)
    ++		    enum object_storage_format object_storage_format)
      {
      	struct object_id oid;
      	char *head;
    @@ builtin/clone.c: static int checkout(int submodule_progress,
      			strvec_pushf(&cmd.args, "--ref-storage-format=%s",
      				     ref_storage_format_to_name(ref_storage_format));
      
    -+		if (object_storage_format_uri)
    ++		if (object_storage_format != OBJECT_STORAGE_FORMAT_UNKNOWN)
     +			strvec_pushf(&cmd.args, "--object-storage-format=%s",
    -+				     object_storage_format_uri);
    ++				     object_storage_format_to_name(object_storage_format));
     +
      		if (filter_submodules && filter_options->choice)
      			strvec_pushf(&cmd.args, "--filter=%s",
      				     expand_list_objects_filter_spec(filter_options));
    +@@ builtin/clone.c: int cmd_clone(int argc,
    + 	int submodule_progress;
    + 	int filter_submodules = 0;
    + 	int hash_algo;
    ++	enum object_storage_format object_storage_format = OBJECT_STORAGE_FORMAT_UNKNOWN;
    + 	enum ref_storage_format ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN;
    + 	const int do_not_override_repo_unix_permissions = -1;
    + 	int option_reject_shallow = -1; /* unspecified */
    +@@ builtin/clone.c: int cmd_clone(int argc,
    + 			die(_("unknown ref storage format '%s'"), ref_storage_format_uri);
    + 	}
    + 
    +-	if (object_storage_format_uri &&
    +-	    object_storage_format_by_uri(object_storage_format_uri, NULL) == OBJECT_STORAGE_FORMAT_UNKNOWN)
    +-		die(_("unknown object storage format '%s'"), object_storage_format_uri);
    ++	if (object_storage_format_uri) {
    ++		object_storage_format = object_storage_format_by_uri(object_storage_format_uri, NULL);
    ++		if (object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
    ++			die(_("unknown object storage format '%s'"), object_storage_format_uri);
    ++	}
    + 
    + 	if (option_mirror) {
    + 		option_bare = 1;
     @@ builtin/clone.c: int cmd_clone(int argc,
      	err = checkout(submodule_progress,
      		       &filter_options,
      		       filter_submodules,
     -		       ref_storage_format);
     +		       ref_storage_format,
    -+		       object_storage_format_uri);
    ++		       object_storage_format);
      
      	list_objects_filter_release(&filter_options);
      
    @@ builtin/submodule--helper.c: struct module_clone_data {
      	int depth;
      	struct list_objects_filter_options *filter_options;
      	enum ref_storage_format ref_storage_format;
    -+	const char *object_storage_format;
    ++	enum object_storage_format object_storage_format;
      	unsigned int quiet: 1;
      	unsigned int progress: 1;
      	unsigned int dissociate: 1;
    +@@ builtin/submodule--helper.c: struct module_clone_data {
    + #define MODULE_CLONE_DATA_INIT { \
    + 	.single_branch = -1, \
    + 	.ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN, \
    ++	.object_storage_format = OBJECT_STORAGE_FORMAT_UNKNOWN, \
    + }
    + 
    + struct submodule_alternate_setup {
     @@ builtin/submodule--helper.c: static int clone_submodule(const struct module_clone_data *clone_data,
      		if (clone_data->ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
      			strvec_pushf(&cp.args, "--ref-storage-format=%s",
      				     ref_storage_format_to_name(clone_data->ref_storage_format));
    -+		if (clone_data->object_storage_format)
    ++		if (clone_data->object_storage_format != OBJECT_STORAGE_FORMAT_UNKNOWN)
     +			strvec_pushf(&cp.args, "--object-storage-format=%s",
    -+				     clone_data->object_storage_format);
    ++				     object_storage_format_to_name(clone_data->object_storage_format));
      		if (clone_data->dissociate)
      			strvec_push(&cp.args, "--dissociate");
      		if (sm_gitdir && *sm_gitdir)
    +@@ builtin/submodule--helper.c: static int module_clone(int argc, const char **argv, const char *prefix,
    + 	struct string_list reference = STRING_LIST_INIT_NODUP;
    + 	struct list_objects_filter_options filter_options =
    + 		LIST_OBJECTS_FILTER_INIT;
    ++	const char *object_storage_format = NULL;
    + 	const char *ref_storage_format = NULL;
    + 
    + 	struct option module_clone_options[] = {
     @@ builtin/submodule--helper.c: static int module_clone(int argc, const char **argv, const char *prefix,
      		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
      			   N_("specify the reference storage format to use")),
      		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
    -+		OPT_STRING(0, "object-storage-format", &clone_data.object_storage_format, N_("format"),
    ++		OPT_STRING(0, "object-storage-format", &object_storage_format, N_("format"),
     +			   N_("specify the object storage format to use")),
      		OPT_BOOL(0, "dissociate", &dissociate,
      			   N_("use --reference only while cloning")),
    @@ builtin/submodule--helper.c: static int module_clone(int argc, const char **argv
      		if (clone_data.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
      			die(_("unknown ref storage format '%s'"), ref_storage_format);
      	}
    -+	if (clone_data.object_storage_format &&
    -+	    odb_source_type_by_uri(clone_data.object_storage_format, NULL) == ODB_SOURCE_UNKNOWN)
    -+		die(_("unknown object storage format '%s'"), clone_data.object_storage_format);
    ++
    ++	if (object_storage_format) {
    ++		clone_data.object_storage_format = object_storage_format_by_name(object_storage_format);
    ++		if (clone_data.object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
    ++			die(_("unknown object storage format '%s'"), object_storage_format);
    ++	}
    ++
      	clone_data.dissociate = !!dissociate;
      	clone_data.quiet = !!quiet;
      	clone_data.progress = !!progress;
     @@ builtin/submodule--helper.c: struct update_data {
    + 	struct submodule_update_strategy update_strategy;
      	struct list_objects_filter_options *filter_options;
      	struct module_list list;
    ++	enum object_storage_format object_storage_format;
      	enum ref_storage_format ref_storage_format;
    -+	const char *object_storage_format;
      	int depth;
      	int max_jobs;
    - 	int single_branch;
     @@ builtin/submodule--helper.c: static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
      	if (suc->update_data->ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
      		strvec_pushf(&child->args, "--ref-storage-format=%s",
      			     ref_storage_format_to_name(suc->update_data->ref_storage_format));
    -+	if (suc->update_data->object_storage_format)
    ++	if (suc->update_data->object_storage_format != OBJECT_STORAGE_FORMAT_UNKNOWN)
     +		strvec_pushf(&child->args, "--object-storage-format=%s",
    -+			     suc->update_data->object_storage_format);
    ++			     object_storage_format_to_name(suc->update_data->object_storage_format));
      	strvec_pushl(&child->args, "--path", sub->path, NULL);
      	strvec_pushl(&child->args, "--name", sub->name, NULL);
      	strvec_pushl(&child->args, "--url", url, NULL);
    @@ builtin/submodule--helper.c: static void update_data_to_args(const struct update
      			     ref_storage_format_to_name(update_data->ref_storage_format));
     +	if (update_data->object_storage_format)
     +		strvec_pushf(args, "--object-storage-format=%s",
    -+			     update_data->object_storage_format);
    ++			     object_storage_format_to_name(update_data->object_storage_format));
      	if (update_data->filter_options && update_data->filter_options->choice)
      		strvec_pushf(args, "--filter=%s",
      				expand_list_objects_filter_spec(
    +@@ builtin/submodule--helper.c: static int module_update(int argc, const char **argv, const char *prefix,
    + 	struct update_data opt = UPDATE_DATA_INIT;
    + 	struct list_objects_filter_options filter_options =
    + 		LIST_OBJECTS_FILTER_INIT;
    ++	const char *object_storage_format = NULL;
    + 	const char *ref_storage_format = NULL;
    + 	int ret;
    + 	struct option module_update_options[] = {
     @@ builtin/submodule--helper.c: static int module_update(int argc, const char **argv, const char *prefix,
      		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
      			   N_("specify the reference storage format to use")),
      		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
    -+		OPT_STRING(0, "object-storage-format", &opt.object_storage_format, N_("format"),
    ++		OPT_STRING(0, "object-storage-format", &object_storage_format, N_("format"),
     +			   N_("specify the object storage format to use")),
      		OPT_BOOL(0, "dissociate", &opt.dissociate,
      			   N_("use --reference only while cloning")),
    @@ builtin/submodule--helper.c: static int module_update(int argc, const char **arg
      		if (opt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
      			die(_("unknown ref storage format '%s'"), ref_storage_format);
      	}
    -+	if (opt.object_storage_format &&
    -+	    odb_source_type_by_uri(opt.object_storage_format, NULL) == ODB_SOURCE_UNKNOWN)
    -+		die(_("unknown object storage format '%s'"), opt.object_storage_format);
    ++	if (object_storage_format) {
    ++		opt.object_storage_format = object_storage_format_by_name(object_storage_format);
    ++		if (opt.object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
    ++			die(_("unknown object storage format '%s'"), object_storage_format);
    ++	}
      
      	opt.filter_options = &filter_options;
      	opt.prefix = prefix;
     @@ builtin/submodule--helper.c: struct add_data {
    + 	const char *sm_name;
      	const char *repo;
      	const char *realrepo;
    ++	enum object_storage_format object_storage_format;
      	enum ref_storage_format ref_storage_format;
    -+	const char *object_storage_format;
      	int depth;
      	unsigned int force: 1;
    - 	unsigned int quiet: 1;
     @@ builtin/submodule--helper.c: static int add_submodule(const struct add_data *add_data)
      			string_list_append(&reference, p)->util = p;
      		}
    @@ builtin/submodule--helper.c: static int add_submodule(const struct add_data *add
      		clone_data.dissociate = add_data->dissociate;
      		if (add_data->depth >= 0)
      			clone_data.depth = add_data->depth;
    +@@ builtin/submodule--helper.c: static int module_add(int argc, const char **argv, const char *prefix,
    + {
    + 	int force = 0, quiet = 0, progress = 0, dissociate = 0;
    + 	struct add_data add_data = ADD_DATA_INIT;
    ++	const char *object_storage_format = NULL;
    + 	const char *ref_storage_format = NULL;
    + 	char *to_free = NULL;
    + 	const struct submodule *existing;
     @@ builtin/submodule--helper.c: static int module_add(int argc, const char **argv, const char *prefix,
      		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
      			   N_("specify the reference storage format to use")),
      		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
    -+		OPT_STRING(0, "object-storage-format", &add_data.object_storage_format, N_("format"),
    ++		OPT_STRING(0, "object-storage-format", &object_storage_format, N_("format"),
     +			   N_("specify the object storage format to use")),
      		OPT_BOOL(0, "dissociate", &dissociate, N_("borrow the objects from reference repositories")),
      		OPT_STRING(0, "name", &add_data.sm_name, N_("name"),
    @@ builtin/submodule--helper.c: static int module_add(int argc, const char **argv,
      		if (add_data.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
      			die(_("unknown ref storage format '%s'"), ref_storage_format);
      	}
    -+	if (add_data.object_storage_format &&
    -+	    odb_source_type_by_uri(add_data.object_storage_format, NULL) == ODB_SOURCE_UNKNOWN)
    -+		die(_("unknown object storage format '%s'"), add_data.object_storage_format);
    ++	if (object_storage_format) {
    ++		add_data.object_storage_format = object_storage_format_by_name(object_storage_format);
    ++		if (add_data.object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
    ++			die(_("unknown object storage format '%s'"), object_storage_format);
    ++	}
      
      	add_data.repo = argv[0];
      	if (argc == 1)
    @@ t/t7427-submodule-object-storage-format.sh (new)
     +	)
     +'
     +
    -+test_expect_success 'add submodules with different object storage format' '
    ++test_expect_success 'add submodules with explicit files storage format' '
     +	test_when_finished "rm -rf submodule upstream" &&
     +
     +	git init submodule &&
     +	test_commit -C submodule submodule-initial &&
     +	git init upstream &&
     +	test_object_storage_format upstream files &&
    -+	git -C upstream submodule add --object-storage-format=files://objs "file://$(pwd)/submodule" &&
    -+	test_object_storage_format upstream/submodule files://objs &&
    -+	test_path_is_dir upstream/.git/modules/submodule/objs/pack
    ++	git -C upstream submodule add --object-storage-format=files "file://$(pwd)/submodule" &&
    ++	test_object_storage_format upstream/submodule files
     +'
     +
     +test_expect_success 'add submodules with garbage object storage format' '
    @@ t/t7427-submodule-object-storage-format.sh (new)
     +	test_cmp expect err
     +'
     +
    ++test_expect_success 'add submodules with object storage payload is rejected' '
    ++	test_when_finished "rm -rf submodule upstream" &&
    ++
    ++	git init submodule &&
    ++	test_commit -C submodule submodule-initial &&
    ++	git init upstream &&
    ++	cat >expect <<-EOF &&
    ++	fatal: unknown object storage format ${SQ}files://objs${SQ}
    ++	EOF
    ++	test_must_fail git -C upstream submodule add --object-storage-format=files://objs "file://$(pwd)/submodule" 2>err &&
    ++	test_cmp expect err
    ++'
    ++
     +test_expect_success 'recursive clone propagates object storage format' '
     +	test_when_finished "rm -rf submodule upstream downstream" &&
     +
    @@ t/t7427-submodule-object-storage-format.sh (new)
     +	git clone --object-storage-format=files://objs --recurse-submodules \
     +		upstream downstream &&
     +	test_object_storage_format downstream files://objs &&
    -+	test_object_storage_format downstream/submodule files://objs &&
    ++	test_object_storage_format downstream/submodule files &&
     +	test_path_is_dir downstream/.git/objs/pack &&
    -+	test_path_is_dir downstream/.git/modules/submodule/objs/pack
    ++	test_path_is_dir downstream/.git/modules/submodule/objects/pack
     +'
     +
    -+test_expect_success 'clone submodules with different object storage format' '
    ++test_expect_success 'clone submodules with explicit object storage format' '
     +	test_when_finished "rm -rf submodule upstream downstream" &&
     +
     +	git init submodule &&
    @@ t/t7427-submodule-object-storage-format.sh (new)
     +	git -C upstream commit -m "upstream submodule" &&
     +
     +	git clone --no-recurse-submodules "file://$(pwd)/upstream" downstream &&
    -+	git -C downstream submodule update --init --object-storage-format=files://objs &&
     +	test_object_storage_format downstream files &&
    -+	test_object_storage_format downstream/submodule files://objs
    ++	git -C downstream submodule update --init --object-storage-format=files &&
    ++	test_object_storage_format downstream/submodule files &&
    ++	test_path_is_dir downstream/.git/modules/submodule/objects/pack
     +'
     +
     +test_done

---
base-commit: 0d31382da9336683214372ba328fe9ecbc8bcb9a
change-id: 20261006-b4-pks-odb-extension-36de693bb943


^ permalink raw reply	[flat|nested] 49+ messages in thread

* [PATCH v3 1/7] loose: drop unused `repo_write_loose_object_map()`
  2026-10-23 11:49 ` [PATCH v3 " Patrick Steinhardt
@ 2026-10-23 11:49   ` Patrick Steinhardt
  2026-10-23 11:49   ` [PATCH v3 2/7] setup: introduce object storage extension Patrick Steinhardt
                     ` (6 subsequent siblings)
  7 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-23 11:49 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

The function `repo_write_loose_object_map()` was introduced in
23b2c7e95b (loose: add a mapping between SHA-1 and SHA-256 for loose
objects, 2023-10-01), but it never had any callers as loose object maps
are instead written incrementally via `repo_add_loose_object_map()`
whenever a new loose object is created.

Besides being dead code, the function also hardcodes the location of the
"loose-object-idx" file as "$GIT_COMMON_DIR/objects", whereas both the
reader and `repo_add_loose_object_map()` derive the location from the
loose source's path. The function would thus disagree with everyone else
where the loose object map is supposed to be located when the object
directory is using a non-default location.

Remove the function.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 loose.c | 46 ----------------------------------------------
 loose.h |  1 -
 2 files changed, 47 deletions(-)

diff --git a/loose.c b/loose.c
index d6a2bb5ba3..7e26a04ec0 100644
--- a/loose.c
+++ b/loose.c
@@ -123,52 +123,6 @@ int repo_read_loose_object_map(struct repository *repo)
 	return 0;
 }
 
-int repo_write_loose_object_map(struct repository *repo)
-{
-	struct odb_source_files *files = odb_source_files_downcast(repo->objects->source);
-	kh_oid_map_t *map = files->dirs->loose->map->to_compat;
-	struct lock_file lock;
-	int fd;
-	khiter_t iter;
-	struct strbuf buf = STRBUF_INIT, path = STRBUF_INIT;
-
-	if (!should_use_loose_object_map(repo))
-		return 0;
-
-	repo_common_path_replace(repo, &path, "objects/loose-object-idx");
-	fd = repo_hold_lock_file_for_update_timeout(repo, &lock, path.buf,
-						    LOCK_DIE_ON_ERROR, -1);
-	iter = kh_begin(map);
-	if (write_in_full(fd, loose_object_header, strlen(loose_object_header)) < 0)
-		goto errout;
-
-	for (; iter != kh_end(map); iter++) {
-		if (kh_exist(map, iter)) {
-			if (oideq(&kh_key(map, iter), repo->hash_algo->empty_tree) ||
-			    oideq(&kh_key(map, iter), repo->hash_algo->empty_blob))
-				continue;
-			strbuf_addf(&buf, "%s %s\n", oid_to_hex(&kh_key(map, iter)), oid_to_hex(kh_value(map, iter)));
-			if (write_in_full(fd, buf.buf, buf.len) < 0)
-				goto errout;
-			strbuf_reset(&buf);
-		}
-	}
-	strbuf_release(&buf);
-	if (commit_lock_file(&lock) < 0) {
-		error_errno(_("could not write loose object index %s"), path.buf);
-		strbuf_release(&path);
-		return -1;
-	}
-	strbuf_release(&path);
-	return 0;
-errout:
-	rollback_lock_file(&lock);
-	strbuf_release(&buf);
-	error_errno(_("failed to write loose object index %s"), path.buf);
-	strbuf_release(&path);
-	return -1;
-}
-
 static int write_one_object(struct odb_source_loose *loose,
 			    const struct object_id *oid,
 			    const struct object_id *compat_oid)
diff --git a/loose.h b/loose.h
index ed663ac550..41c4b46818 100644
--- a/loose.h
+++ b/loose.h
@@ -22,6 +22,5 @@ int repo_add_loose_object_map(struct odb_source_loose *loose,
 			      const struct object_id *oid,
 			      const struct object_id *compat_oid);
 int repo_read_loose_object_map(struct repository *repo);
-int repo_write_loose_object_map(struct repository *repo);
 
 #endif

-- 
2.56.0.307.g5fe7bd680c.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v3 2/7] setup: introduce object storage extension
  2026-10-23 11:49 ` [PATCH v3 " Patrick Steinhardt
  2026-10-23 11:49   ` [PATCH v3 1/7] loose: drop unused `repo_write_loose_object_map()` Patrick Steinhardt
@ 2026-10-23 11:49   ` Patrick Steinhardt
  2026-10-24 14:50     ` SZEDER Gábor
  2026-10-26 11:22     ` Karthik Nayak
  2026-10-23 11:49   ` [PATCH v3 3/7] setup: allow overriding object storage format via environment Patrick Steinhardt
                     ` (5 subsequent siblings)
  7 siblings, 2 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-23 11:49 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

Introduce a new "extensions.objectStorage" extension that allows the
user to configure the primary object database source of a repository.
The value is a URI in the format "<schema>[://<payload>]", mirroring
the format used by "extensions.refStorage":

  - The schema identifies the backend that shall be used. For now the
    only supported schema is "files", which is the existing object
    database backed by loose objects and packfiles.

  - The payload can be passed to configure the backend, and its
    interpretation is backend-specific. For the "files" backend, it
    allows you to configure a different location for the ".git/objects"
    directory.

Eventually, this mechanism will be used to support alternative backends.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/config/extensions.adoc     | 23 +++++++++++++++++++
 Documentation/object-storage-format.adoc |  6 +++++
 odb.c                                    | 39 +++++++++++++++++++++++++++++++-
 odb.h                                    | 12 ++++++++++
 odb/source-files.c                       |  7 ++++++
 odb/source-files.h                       |  1 +
 odb/source.c                             | 14 +++++++++++-
 odb/source.h                             |  5 +++-
 repository.c                             | 10 ++++++++
 repository.h                             | 18 +++++++++++++++
 setup.c                                  | 19 ++++++++++++++++
 setup.h                                  |  3 +++
 t/t0001-init.sh                          | 30 ++++++++++++++++++++++++
 t/unit-tests/u-odb-inmemory.c            |  1 +
 14 files changed, 185 insertions(+), 3 deletions(-)

diff --git a/Documentation/config/extensions.adoc b/Documentation/config/extensions.adoc
index be6678bb5b..4f3a66a454 100644
--- a/Documentation/config/extensions.adoc
+++ b/Documentation/config/extensions.adoc
@@ -56,6 +56,29 @@ preciousObjects:::
 For historical reasons, this extension is respected regardless of the
 `core.repositoryFormatVersion` setting.
 
+objectStorage:::
+	Specify the object storage format and a corresponding payload. The
+	value can be either a format name or a URI:
++
+--
+* A format name alone (e.g., `files`).
+
+* A URI format `<format>://<payload>` explicitly specifies both the
+  format and payload (e.g., `files:///foo/bar`).
+
+Supported formats are:
+
+include::../object-storage-format.adoc[]
+
+--
++
+Note that this setting should only be set by linkgit:git-init[1] or
+linkgit:git-clone[1]. Trying to change it after initialization will make
+preexisting objects inaccessible.
++
+NOTE: This extension is still considered EXPERIMENTAL and may be subject to
+change.
+
 refStorage:::
 	Specify the ref storage format and a corresponding payload. The value
 	can be either a format name or a URI:
diff --git a/Documentation/object-storage-format.adoc b/Documentation/object-storage-format.adoc
new file mode 100644
index 0000000000..4c4aef6dda
--- /dev/null
+++ b/Documentation/object-storage-format.adoc
@@ -0,0 +1,6 @@
+`files[://<path>]`;; for loose objects and packfiles. This is the default.
++
+The optional payload can be passed to specify an alternative filesystem path
+where the objects will be stored. Relative paths are resolved relative to the
+`$GIT_COMMON_DIR`. Defaults to `$GIT_COMMON_DIR/objects` when no payload is
+provided. The payload is ignored when `GIT_OBJECT_DIRECTORY` is set.
diff --git a/odb.c b/odb.c
index 98c5c862f5..0649622bc6 100644
--- a/odb.c
+++ b/odb.c
@@ -27,6 +27,42 @@
 #include "trace2.h"
 #include "write-or-die.h"
 
+static const char * const object_storage_name_by_format[] = {
+	[OBJECT_STORAGE_FORMAT_UNKNOWN] = "unknown",
+	[OBJECT_STORAGE_FORMAT_FILES] = "files",
+};
+
+enum object_storage_format object_storage_format_by_name(const char *name)
+{
+	for (size_t i = 0; i < ARRAY_SIZE(object_storage_name_by_format); i++)
+		if (!strcmp(object_storage_name_by_format[i], name))
+			return i;
+	return OBJECT_STORAGE_FORMAT_UNKNOWN;
+}
+
+enum object_storage_format object_storage_format_by_uri(const char *uri, char **payload)
+{
+	enum object_storage_format format;
+	const char *schema_end;
+	char *name;
+
+	schema_end = strstr(uri, "://");
+	if (!schema_end) {
+		name = xstrdup(uri);
+		if (payload)
+			*payload = NULL;
+	} else {
+		name = xstrndup(uri, schema_end - uri);
+		if (payload)
+			*payload = *(schema_end + 3) ? xstrdup(schema_end + 3) : NULL;
+	}
+
+	format = object_storage_format_by_name(name);
+
+	free(name);
+	return format;
+}
+
 int odb_mkstemp(struct object_database *odb,
 		struct strbuf *temp_filename, const char *pattern)
 {
@@ -757,7 +793,8 @@ struct object_database *odb_new(struct repository *repo,
 	o->repo = repo;
 	pthread_mutex_init(&o->replace_mutex, NULL);
 
-	o->source = odb_source_new(o, flags);
+	o->source = odb_source_new(o, repo->object_storage_format,
+				   repo->object_storage_payload, flags);
 	o->inmemory_objects = &odb_source_inmemory_new(o)->base;
 
 	return o;
diff --git a/odb.h b/odb.h
index 0ccc47ee73..cb0505334a 100644
--- a/odb.h
+++ b/odb.h
@@ -6,6 +6,7 @@
 #include "oid-array.h"
 #include "oidset.h"
 #include "oidmap.h"
+#include "repository.h"
 #include "string-list.h"
 #include "thread-utils.h"
 
@@ -26,6 +27,17 @@ struct strvec;
  */
 char *compute_alternate_path(const char *path, struct strbuf *err);
 
+/*
+ * Parse the given URI in the format "<schema>[://<payload>]" and return the
+ * object storage format identified by the schema. If `payload` is non-NULL it
+ * will be set to a newly allocated copy of the payload, or NULL if the URI has
+ * no or an empty payload. Returns `OBJECT_STORAGE_FORMAT_UNKNOWN` for unknown
+ * schemas.
+ */
+enum object_storage_format object_storage_format_by_uri(const char *uri, char **payload);
+
+enum object_storage_format object_storage_format_by_name(const char *name);
+
 /*
  * The object database encapsulates access to objects in a repository. It
  * manages the object source as well as auxiliary data structures required to
diff --git a/odb/source-files.c b/odb/source-files.c
index 455f409db0..4a7cbbfd9b 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -1235,6 +1235,7 @@ struct odb_files_dir *odb_source_files_find_dir(struct object_database *odb, con
 }
 
 struct odb_source_files *odb_source_files_new(struct object_database *odb,
+					      const char *payload,
 					      enum odb_new_flags flags)
 {
 	struct odb_source_files *files;
@@ -1245,6 +1246,12 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
 		object_dir = xstrdup_or_null(getenv(DB_ENVIRONMENT));
 		alternates = xstrdup_or_null(getenv(ALTERNATE_DB_ENVIRONMENT));
 	}
+	if (!object_dir && payload) {
+		if (is_absolute_path(payload))
+			object_dir = xstrdup(payload);
+		else
+			object_dir = xstrfmt("%s/%s", odb->repo->commondir, payload);
+	}
 	if (!object_dir)
 		object_dir = xstrfmt("%s/objects", odb->repo->commondir);
 
diff --git a/odb/source-files.h b/odb/source-files.h
index 837e890304..71e8db84ff 100644
--- a/odb/source-files.h
+++ b/odb/source-files.h
@@ -70,6 +70,7 @@ struct odb_source_files {
 
 /* Allocate and initialize a new object source. */
 struct odb_source_files *odb_source_files_new(struct object_database *odb,
+					      const char *payload,
 					      enum odb_new_flags flags);
 
 /*
diff --git a/odb/source.c b/odb/source.c
index b25ef14df8..a2a6d97ccd 100644
--- a/odb/source.c
+++ b/odb/source.c
@@ -24,9 +24,21 @@ const char *odb_source_type_to_name(enum odb_source_type type)
 }
 
 struct odb_source *odb_source_new(struct object_database *odb,
+				  enum object_storage_format format,
+				  const char *payload,
 				  enum odb_new_flags flags)
 {
-	return &odb_source_files_new(odb, flags)->base;
+	struct odb_source *source;
+
+	switch (format) {
+	case OBJECT_STORAGE_FORMAT_FILES:
+		source = &odb_source_files_new(odb, payload, flags)->base;
+		break;
+	default:
+		BUG("unknown object storage format");
+	}
+
+	return source;
 }
 
 void odb_source_init(struct odb_source *source,
diff --git a/odb/source.h b/odb/source.h
index 1867347dfc..1ab3208888 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -304,9 +304,12 @@ struct odb_source {
 
 /*
  * Allocate and initialize a new source for the given object database. The path
- * of the source is derived from repository paths.
+ * of the source is derived from repository paths or from the object storage
+ * format.
  */
 struct odb_source *odb_source_new(struct object_database *odb,
+				  enum object_storage_format format,
+				  const char *payload,
 				  enum odb_new_flags flags);
 
 /*
diff --git a/repository.c b/repository.c
index 970d181828..77ecf6b604 100644
--- a/repository.c
+++ b/repository.c
@@ -217,6 +217,15 @@ void repo_set_ref_storage_format(struct repository *repo,
 	repo->ref_storage_payload = xstrdup_or_null(payload);
 }
 
+void repo_set_object_storage_format(struct repository *repo,
+				    enum object_storage_format format,
+				    const char *payload)
+{
+	repo->object_storage_format = format;
+	free(repo->object_storage_payload);
+	repo->object_storage_payload = xstrdup_or_null(payload);
+}
+
 /*
  * Attempt to resolve and set the provided 'gitdir' for repository 'repo'.
  * Return 0 upon success and a non-zero value upon failure.
@@ -382,6 +391,7 @@ void repo_clear(struct repository *repo)
 	free(repo->worktree);
 	free(repo->submodule_prefix);
 	free(repo->ref_storage_payload);
+	free(repo->object_storage_payload);
 
 	odb_free(repo->objects);
 
diff --git a/repository.h b/repository.h
index 2a348012e8..8c4437762e 100644
--- a/repository.h
+++ b/repository.h
@@ -22,6 +22,11 @@ enum ref_storage_format {
 	REF_STORAGE_FORMAT_REFTABLE,
 };
 
+enum object_storage_format {
+	OBJECT_STORAGE_FORMAT_UNKNOWN,
+	OBJECT_STORAGE_FORMAT_FILES,
+};
+
 #ifdef WITH_BREAKING_CHANGES /* Git 3.0 */
 # define REF_STORAGE_FORMAT_DEFAULT REF_STORAGE_FORMAT_REFTABLE
 #else
@@ -178,6 +183,16 @@ struct repository {
 	 */
 	char *ref_storage_payload;
 
+	/* Repository's object storage format, as serialized on disk. */
+	enum object_storage_format object_storage_format;
+
+	/*
+	 * Object storage information as needed for the backend. This contains
+	 * only the payload from the object storage format URI without the
+	 * schema.
+	 */
+	char *object_storage_payload;
+
 	/* A unique-id for tracing purposes. */
 	int trace2_repo_id;
 
@@ -255,6 +270,9 @@ void repo_set_compat_hash_algo(struct repository *repo, uint32_t compat_algo);
 void repo_set_ref_storage_format(struct repository *repo,
 				 enum ref_storage_format format,
 				 const char *payload);
+void repo_set_object_storage_format(struct repository *repo,
+				    enum object_storage_format format,
+				    const char *payload);
 void initialize_repository(struct repository *repo);
 RESULT_MUST_BE_USED
 int repo_init(struct repository *r, const char *gitdir, const char *worktree);
diff --git a/setup.c b/setup.c
index 3ee19f3e3c..249bd67876 100644
--- a/setup.c
+++ b/setup.c
@@ -686,6 +686,20 @@ static enum extension_result handle_extension(const char *var,
 			return error(_("invalid value for '%s': '%s'"),
 				     "extensions.refstorage", value);
 		data->ref_storage_format = format;
+		return EXTENSION_OK;
+	} else if (!strcmp(ext, "objectstorage")) {
+		enum object_storage_format format;
+
+		if (!value)
+			return config_error_nonbool(var);
+
+		FREE_AND_NULL(data->object_storage_payload);
+		format = object_storage_format_by_uri(value, &data->object_storage_payload);
+		if (format == OBJECT_STORAGE_FORMAT_UNKNOWN)
+			return error(_("invalid value for '%s': '%s'"),
+				     "extensions.objectstorage", value);
+		data->object_storage_format = format;
+
 		return EXTENSION_OK;
 	} else if (!strcmp(ext, "relativeworktrees")) {
 		data->relative_worktrees = git_config_bool(var, value);
@@ -694,6 +708,7 @@ static enum extension_result handle_extension(const char *var,
 		data->submodule_path_cfg = git_config_bool(var, value);
 		return EXTENSION_OK;
 	}
+
 	return EXTENSION_UNKNOWN;
 }
 
@@ -864,6 +879,7 @@ void clear_repository_format(struct repository_format *format)
 	free(format->work_tree);
 	free(format->partial_clone);
 	free(format->ref_storage_payload);
+	free(format->object_storage_payload);
 	init_repository_format(format);
 }
 
@@ -1786,6 +1802,9 @@ int apply_repository_format(struct repository *repo,
 	repo_set_ref_storage_format(repo,
 				    format->ref_storage_format,
 				    format->ref_storage_payload);
+	repo_set_object_storage_format(repo,
+				       format->object_storage_format,
+				       format->object_storage_payload);
 	repo->repository_format_worktree_config =
 		format->worktree_config;
 	repo->repository_format_submodule_path_cfg =
diff --git a/setup.h b/setup.h
index 7a3efe5e48..a4afa8910e 100644
--- a/setup.h
+++ b/setup.h
@@ -190,6 +190,8 @@ struct repository_format {
 	int compat_hash_algo;
 	enum ref_storage_format ref_storage_format;
 	char *ref_storage_payload;
+	enum object_storage_format object_storage_format;
+	char *object_storage_payload;
 	int sparse_index;
 	char *work_tree;
 	struct string_list unknown_extensions;
@@ -207,6 +209,7 @@ struct repository_format {
 	.is_bare = -1, \
 	.hash_algo = GIT_HASH_DEFAULT, \
 	.ref_storage_format = REF_STORAGE_FORMAT_FILES, \
+	.object_storage_format = OBJECT_STORAGE_FORMAT_FILES, \
 	.unknown_extensions = STRING_LIST_INIT_DUP, \
 	.v1_only_extensions = STRING_LIST_INIT_DUP, \
 }
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index f1d227f271..c7c464d3a2 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -839,6 +839,36 @@ test_expect_success 'init with --ref-storage-format=garbage' '
 	test_cmp expect err
 '
 
+test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage is not allowed with repo version 0' '
+	test_when_finished "rm -rf objstorage" &&
+	git init objstorage &&
+	git -C objstorage config set extensions.objectStorage files &&
+	test_must_fail git -C objstorage rev-parse 2>err &&
+	test_grep "repo version is 0, but v1-only extension found" err
+'
+
+test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with files backend' '
+	test_when_finished "rm -rf objstorage" &&
+	git init objstorage &&
+	git -C objstorage config set core.repositoryformatversion 1 &&
+	git -C objstorage config set extensions.objectStorage files &&
+	test_commit -C objstorage A &&
+	git -C objstorage rev-parse --verify HEAD
+'
+
+test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with unknown backend' '
+	test_when_finished "rm -rf objstorage" &&
+	git init objstorage &&
+	git -C objstorage config set core.repositoryformatversion 1 &&
+	git -C objstorage config set extensions.objectStorage garbage &&
+	test_must_fail git -C objstorage rev-parse 2>err &&
+	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage${SQ}" err &&
+
+	git config --file objstorage/.git/config extensions.objectStorage garbage://payload &&
+	test_must_fail git -C objstorage rev-parse 2>err &&
+	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage://payload${SQ}" err
+'
+
 test_expect_success MINGW 'core.hidedotfiles = false' '
 	git config --global core.hidedotfiles false &&
 	rm -rf newdir &&
diff --git a/t/unit-tests/u-odb-inmemory.c b/t/unit-tests/u-odb-inmemory.c
index a8da278c68..c4412949c1 100644
--- a/t/unit-tests/u-odb-inmemory.c
+++ b/t/unit-tests/u-odb-inmemory.c
@@ -12,6 +12,7 @@
 
 static struct repository repo = {
 	.hash_algo = &hash_algos[GIT_HASH_SHA1],
+	.object_storage_format = OBJECT_STORAGE_FORMAT_FILES,
 };
 static struct object_database *odb;
 

-- 
2.56.0.307.g5fe7bd680c.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v3 3/7] setup: allow overriding object storage format via environment
  2026-10-23 11:49 ` [PATCH v3 " Patrick Steinhardt
  2026-10-23 11:49   ` [PATCH v3 1/7] loose: drop unused `repo_write_loose_object_map()` Patrick Steinhardt
  2026-10-23 11:49   ` [PATCH v3 2/7] setup: introduce object storage extension Patrick Steinhardt
@ 2026-10-23 11:49   ` Patrick Steinhardt
  2026-10-26 11:25     ` Karthik Nayak
  2026-10-23 11:49   ` [PATCH v3 4/7] builtin: expose the object storage format Patrick Steinhardt
                     ` (4 subsequent siblings)
  7 siblings, 1 reply; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-23 11:49 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

The ref storage format configured via "extensions.refStorage" can be
overridden with the GIT_REF_STORAGE_FORMAT environment variable.
Introduce the GIT_OBJECT_STORAGE_FORMAT environment variable that
overrides "extensions.objectStorage" in the same way.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git.adoc |  5 +++++
 environment.h          |  1 +
 setup.c                | 16 ++++++++++++++++
 t/t0001-init.sh        | 26 ++++++++++++++++++++++++++
 4 files changed, 48 insertions(+)

diff --git a/Documentation/git.adoc b/Documentation/git.adoc
index 3abb4701c3..34812131bb 100644
--- a/Documentation/git.adoc
+++ b/Documentation/git.adoc
@@ -609,6 +609,11 @@ double-quotes and respecting backslash escapes. E.g., the value
 `GIT_REFERENCE_BACKEND`::
 	Deprecated alias of `GIT_REF_STORAGE_FORMAT`.
 
+`GIT_OBJECT_STORAGE_FORMAT`::
+	Specify which object storage format to use along with its URI.
+	See `extensions.objectStorage` option in linkgit:git-config[1] for more
+	details. Overrides the config variable when used.
+
 Git Commits
 ~~~~~~~~~~~
 `GIT_AUTHOR_NAME`::
diff --git a/environment.h b/environment.h
index b336459e90..c26ef92b52 100644
--- a/environment.h
+++ b/environment.h
@@ -45,6 +45,7 @@
 #define GIT_ATTR_SOURCE_ENVIRONMENT "GIT_ATTR_SOURCE"
 #define GIT_REFERENCE_BACKEND_ENVIRONMENT "GIT_REFERENCE_BACKEND"
 #define GIT_REF_STORAGE_FORMAT_ENVIRONMENT "GIT_REF_STORAGE_FORMAT"
+#define GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT "GIT_OBJECT_STORAGE_FORMAT"
 
 /*
  * Environment variable used to propagate the --no-advice global option to the
diff --git a/setup.c b/setup.c
index 249bd67876..70aa90a9cb 100644
--- a/setup.c
+++ b/setup.c
@@ -2079,6 +2079,7 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
 
 		if (startup_info->have_repository) {
 			struct strbuf err = STRBUF_INIT;
+			const char *object_storage_uri;
 			const char *ref_backend_uri;
 
 			/*
@@ -2097,6 +2098,21 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
 					die(_("unknown ref storage format: '%s'"), ref_backend_uri);
 			}
 
+			/*
+			 * Likewise, the GIT_OBJECT_STORAGE_FORMAT environment
+			 * variable should override the repository config for
+			 * 'extensions.objectStorage'.
+			 */
+			object_storage_uri = getenv(GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT);
+			if (object_storage_uri) {
+				FREE_AND_NULL(discovery.format.object_storage_payload);
+				discovery.format.object_storage_format =
+					object_storage_format_by_uri(object_storage_uri,
+								     &discovery.format.object_storage_payload);
+				if (discovery.format.object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
+					die(_("unknown object storage format: '%s'"), object_storage_uri);
+			}
+
 			if (apply_repository_format(repo, &discovery.format,
 						    APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)
 				die("%s", err.buf);
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index c7c464d3a2..5e7c25e42c 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -869,6 +869,32 @@ test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with unknown b
 	test_grep "invalid value for ${SQ}extensions.objectstorage${SQ}: ${SQ}garbage://payload${SQ}" err
 '
 
+test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FORMAT overrides extensions.objectStorage' '
+	test_when_finished "rm -rf objstorage" &&
+	git init objstorage &&
+	(
+		cd objstorage &&
+		git config set core.repositoryformatversion 1 &&
+		git config set extensions.objectStorage files://objs &&
+		mkdir .git/objs .git/other &&
+
+		echo content >file &&
+		oid=$(git hash-object -w file) &&
+		test_path_is_file .git/objs/$(test_oid_to_path "$oid") &&
+		git cat-file -e "$oid" &&
+
+		test_must_fail env GIT_OBJECT_STORAGE_FORMAT=files://other git cat-file -e "$oid" &&
+		GIT_OBJECT_STORAGE_FORMAT=files://other git hash-object -w file &&
+		test_path_is_file .git/other/$(test_oid_to_path "$oid") &&
+
+		cat >expect <<-EOF &&
+		fatal: unknown object storage format: ${SQ}garbage${SQ}
+		EOF
+		test_must_fail env GIT_OBJECT_STORAGE_FORMAT=garbage git rev-parse 2>err &&
+		test_cmp expect err
+	)
+'
+
 test_expect_success MINGW 'core.hidedotfiles = false' '
 	git config --global core.hidedotfiles false &&
 	rm -rf newdir &&

-- 
2.56.0.307.g5fe7bd680c.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v3 4/7] builtin: expose the object storage format
  2026-10-23 11:49 ` [PATCH v3 " Patrick Steinhardt
                     ` (2 preceding siblings ...)
  2026-10-23 11:49   ` [PATCH v3 3/7] setup: allow overriding object storage format via environment Patrick Steinhardt
@ 2026-10-23 11:49   ` Patrick Steinhardt
  2026-10-23 11:49   ` [PATCH v3 5/7] builtin/init: introduce handling of " Patrick Steinhardt
                     ` (3 subsequent siblings)
  7 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-23 11:49 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

The ref storage format of a repository can be inspected via both
git-rev-parse(1)'s "--show-ref-storage-format" and git-repo(1)'s
"references.storageFormat" field. Introduce the equivalent options for
the object storage format, as well.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-repo.adoc      |  6 ++++++
 Documentation/git-rev-parse.adoc |  5 +++++
 builtin/repo.c                   | 10 ++++++++++
 builtin/rev-parse.c              |  9 +++++++++
 odb.c                            |  7 +++++++
 odb.h                            |  1 +
 t/t0001-init.sh                  |  5 ++++-
 t/t1900-repo-info.sh             |  3 +++
 8 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index cb8c1e7292..d2dc9b7a69 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -105,6 +105,12 @@ values that they return:
 `object.format`::
 	The object format (hash algorithm) used in the repository.
 
+`objects.storageFormat`::
+	The object storage format used in the repository, as configured via
+	`extensions.objectStorage`. The valid values are:
++
+include::object-storage-format.adoc[]
+
 `path.commondir.absolute`::
 	The canonical absolute path to the Git repository's common
 	directory (the shared `.git` directory containing objects,
diff --git a/Documentation/git-rev-parse.adoc b/Documentation/git-rev-parse.adoc
index 5b866af66a..9103d54d53 100644
--- a/Documentation/git-rev-parse.adoc
+++ b/Documentation/git-rev-parse.adoc
@@ -338,6 +338,11 @@ The following options are unaffected by `--path-format`:
 --show-ref-format::
 	Deprecated alias of `--show-ref-storage-format`.
 
+--show-object-storage-format::
+	Show the object storage format used for the repository. Prints the
+	value of `extensions.objectStorage`, or `files` when the extension is
+	not set.
+
 
 Other Options
 ~~~~~~~~~~~~~
diff --git a/builtin/repo.c b/builtin/repo.c
index 84e012f83f..b73dff898f 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -128,11 +128,21 @@ static int get_references_format(struct repository *repo, struct strbuf *buf)
 	return 0;
 }
 
+static int get_object_storage_format(struct repository *repo, struct strbuf *buf)
+{
+	strbuf_addstr(buf,
+		      object_storage_format_to_name(repo->object_storage_format));
+	if (repo->object_storage_payload)
+		strbuf_addf(buf, "://%s", repo->object_storage_payload);
+	return 0;
+}
+
 /* repo_info_field keys must be in lexicographical order */
 static const struct repo_info_field repo_info_field[] = {
 	{ "layout.bare", get_layout_bare },
 	{ "layout.shallow", get_layout_shallow },
 	{ "object.format", get_object_format },
+	{ "objects.storageFormat", get_object_storage_format },
 	{ "path.commondir.absolute", get_path_commondir_absolute },
 	{ "path.commondir.relative", get_path_commondir_relative },
 	{ "path.gitdir.absolute", get_path_gitdir_absolute },
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index ec33c19bdf..d2154eac8d 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -17,6 +17,7 @@
 #include "gettext.h"
 #include "hash.h"
 #include "hex.h"
+#include "odb.h"
 #include "refs.h"
 #include "quote.h"
 #include "object-name.h"
@@ -1138,6 +1139,14 @@ int cmd_rev_parse(int argc,
 				puts(ref_storage_format_to_name(the_repository->ref_storage_format));
 				continue;
 			}
+			if (!strcmp(arg, "--show-object-storage-format")) {
+				const char *format = object_storage_format_to_name(the_repository->object_storage_format);
+				if (the_repository->object_storage_payload)
+					printf("%s://%s\n", format, the_repository->object_storage_payload);
+				else
+					puts(format);
+				continue;
+			}
 			if (!strcmp(arg, "--end-of-options")) {
 				seen_end_of_options = 1;
 				if (filter & (DO_FLAGS | DO_REVS))
diff --git a/odb.c b/odb.c
index 0649622bc6..a0866e6c88 100644
--- a/odb.c
+++ b/odb.c
@@ -40,6 +40,13 @@ enum object_storage_format object_storage_format_by_name(const char *name)
 	return OBJECT_STORAGE_FORMAT_UNKNOWN;
 }
 
+const char *object_storage_format_to_name(enum object_storage_format format)
+{
+	if (format < 0 || format >= ARRAY_SIZE(object_storage_name_by_format))
+		format = OBJECT_STORAGE_FORMAT_UNKNOWN;
+	return object_storage_name_by_format[format];
+}
+
 enum object_storage_format object_storage_format_by_uri(const char *uri, char **payload)
 {
 	enum object_storage_format format;
diff --git a/odb.h b/odb.h
index cb0505334a..1340f60632 100644
--- a/odb.h
+++ b/odb.h
@@ -37,6 +37,7 @@ char *compute_alternate_path(const char *path, struct strbuf *err);
 enum object_storage_format object_storage_format_by_uri(const char *uri, char **payload);
 
 enum object_storage_format object_storage_format_by_name(const char *name);
+const char *object_storage_format_to_name(enum object_storage_format format);
 
 /*
  * The object database encapsulates access to objects in a repository. It
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 5e7c25e42c..d080ab04a8 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -853,7 +853,10 @@ test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with files bac
 	git -C objstorage config set core.repositoryformatversion 1 &&
 	git -C objstorage config set extensions.objectStorage files &&
 	test_commit -C objstorage A &&
-	git -C objstorage rev-parse --verify HEAD
+	git -C objstorage rev-parse --verify HEAD &&
+	echo files >expect &&
+	git -C objstorage rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
 '
 
 test_expect_success DEFAULT_REPO_FORMAT 'extensions.objectStorage with unknown backend' '
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index d115d2d9f9..493a78f230 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -69,6 +69,9 @@ test_repo_info 'object.format = sha1 is retrieved correctly' \
 test_repo_info 'object.format = sha256 is retrieved correctly' \
 	'git init --object-format=sha256' 'sha256' 'object.format' 'sha256'
 
+test_repo_info 'objects.storageFormat = files is retrieved correctly' \
+	'git init' 'objects-files' 'objects.storageFormat' 'files'
+
 test_expect_success 'values returned in order requested' '
 	cat >expect <<-\EOF &&
 	layout.bare=false

-- 
2.56.0.307.g5fe7bd680c.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v3 5/7] builtin/init: introduce handling of object storage format
  2026-10-23 11:49 ` [PATCH v3 " Patrick Steinhardt
                     ` (3 preceding siblings ...)
  2026-10-23 11:49   ` [PATCH v3 4/7] builtin: expose the object storage format Patrick Steinhardt
@ 2026-10-23 11:49   ` Patrick Steinhardt
  2026-10-23 11:49   ` [PATCH v3 6/7] builtin/clone: introduce "--object-storage-format=" Patrick Steinhardt
                     ` (2 subsequent siblings)
  7 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-23 11:49 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

While the object storage format extension can already be configured
manually via git-config(1), this isn't exactly an ergonomic way of doing
so. For the reference storage format extension, users have a host of
options available to them.

Introduce the equivalent options for the object storage format.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/config/init.adoc |   5 ++
 Documentation/git-init.adoc    |  10 ++-
 Documentation/git.adoc         |   5 ++
 builtin/clone.c                |   9 ++-
 builtin/init-db.c              |   7 +-
 refs.c                         |   3 +-
 setup.c                        |  98 ++++++++++++++++++++++--
 setup.h                        |   2 +
 t/t0001-init.sh                | 166 +++++++++++++++++++++++++++++++++++++++++
 9 files changed, 293 insertions(+), 12 deletions(-)

diff --git a/Documentation/config/init.adoc b/Documentation/config/init.adoc
index e45bd692db..dbd9c663ee 100644
--- a/Documentation/config/init.adoc
+++ b/Documentation/config/init.adoc
@@ -20,6 +20,11 @@ endif::[]
 	precedence over this config.
 `init.defaultRefFormat`::
 	Deprecated alias of `init.defaultRefStorageFormat`.
+`init.defaultObjectStorageFormat`::
+	Allows overriding the default object storage format for new
+	repositories. See `--object-storage-format=` in linkgit:git-init[1].
+	Both the command line option and the `GIT_DEFAULT_OBJECT_STORAGE_FORMAT`
+	environment variable take precedence over this config.
 
 init.defaultSubmodulePathConfig::
 	A boolean that specifies if `git init` and `git clone` should
diff --git a/Documentation/git-init.adoc b/Documentation/git-init.adoc
index b02af22ad1..9e022a7ba8 100644
--- a/Documentation/git-init.adoc
+++ b/Documentation/git-init.adoc
@@ -11,7 +11,7 @@ SYNOPSIS
 [synopsis]
 git init [-q | --quiet] [--bare] [--template=<template-directory>]
 	 [--separate-git-dir <git-dir>] [--object-format=<format>]
-	 [--ref-storage-format=<format>]
+	 [--ref-storage-format=<format>] [--object-storage-format=<format>]
 	 [-b <branch-name> | --initial-branch=<branch-name>]
 	 [--shared[=<permissions>]] [<directory>]
 
@@ -68,6 +68,14 @@ include::ref-storage-format.adoc[]
 `--ref-format=<format>`::
 Deprecated alias of `--ref-storage-format=<format>`.
 
+`--object-storage-format=<format>`::
+Specify the given object storage _<format>_ for the repository. Backends that
+require additional configuration accept a payload in the form
+`<format>://<payload>`, for example a path or a connection string identifying
+the database that shall store the objects. The valid values are:
++
+include::object-storage-format.adoc[]
+
 `--template=<template-directory>`::
 Specify the directory from which templates will be used.  (See the "TEMPLATE
 DIRECTORY" section below.)
diff --git a/Documentation/git.adoc b/Documentation/git.adoc
index 34812131bb..67de5646f0 100644
--- a/Documentation/git.adoc
+++ b/Documentation/git.adoc
@@ -609,6 +609,11 @@ double-quotes and respecting backslash escapes. E.g., the value
 `GIT_REFERENCE_BACKEND`::
 	Deprecated alias of `GIT_REF_STORAGE_FORMAT`.
 
+`GIT_DEFAULT_OBJECT_STORAGE_FORMAT`::
+	If this variable is set, the default object storage format for new
+	repositories will be set to this value. The default is "files".
+	See `--object-storage-format` in linkgit:git-init[1].
+
 `GIT_OBJECT_STORAGE_FORMAT`::
 	Specify which object storage format to use along with its URI.
 	See `extensions.objectStorage` option in linkgit:git-config[1] for more
diff --git a/builtin/clone.c b/builtin/clone.c
index 6047501dfc..95e641b1a4 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1214,7 +1214,7 @@ int cmd_clone(int argc,
 	 */
 	create_repository(the_repository, git_dir, real_git_dir, work_tree,
 			  option_template, GIT_HASH_UNKNOWN, ref_storage_format_uri,
-			  do_not_override_repo_unix_permissions, NULL);
+			  NULL, do_not_override_repo_unix_permissions, NULL);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
@@ -1256,7 +1256,8 @@ int cmd_clone(int argc,
 	 * This is sufficient for Git commands to discover the Git directory.
 	 */
 	initialize_repository_version(the_repository, GIT_HASH_UNKNOWN,
-				      the_repository->ref_storage_format, 1);
+				      the_repository->ref_storage_format,
+				      the_repository->object_storage_format, 1);
 
 	refs_create_refdir_stubs(the_repository, git_dir, NULL);
 
@@ -1468,7 +1469,9 @@ int cmd_clone(int argc,
 	 * ours to the same thing.
 	 */
 	hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));
-	initialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);
+	initialize_repository_version(the_repository, hash_algo,
+				      the_repository->ref_storage_format,
+				      the_repository->object_storage_format, 1);
 	repo_set_hash_algo(the_repository, hash_algo);
 	create_reference_database(the_repository, NULL, 1);
 	startup_info->have_repository = 1;
diff --git a/builtin/init-db.c b/builtin/init-db.c
index 0abdbca8e9..71b748f2f2 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -57,7 +57,7 @@ static int shared_callback(const struct option *opt, const char *arg, int unset)
 static const char *const init_db_usage[] = {
 	N_("git init [-q | --quiet] [--bare] [--template=<template-directory>]\n"
 	   "         [--separate-git-dir <git-dir>] [--object-format=<format>]\n"
-	   "         [--ref-storage-format=<format>]\n"
+	   "         [--ref-storage-format=<format>] [--object-storage-format=<format>]\n"
 	   "         [-b <branch-name> | --initial-branch=<branch-name>]\n"
 	   "         [--shared[=<permissions>]] [<directory>]"),
 	NULL
@@ -84,6 +84,7 @@ int cmd_init_db(int argc,
 	int bare = startup_info->force_bare_repository ? 1 : -1;
 	const char *object_format = NULL;
 	const char *ref_storage_format_uri = NULL;
+	const char *object_storage_format_uri = NULL;
 	const char *initial_branch = NULL;
 	int hash_algo = GIT_HASH_UNKNOWN;
 	int init_shared_repository = -1;
@@ -111,6 +112,8 @@ int cmd_init_db(int argc,
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format_uri, N_("format"),
 			   N_("specify the reference storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
+		OPT_STRING(0, "object-storage-format", &object_storage_format_uri, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_END()
 	};
 	int reinit;
@@ -232,7 +235,7 @@ int cmd_init_db(int argc,
 
 	create_repository(the_repository, git_dir, real_git_dir, work_tree,
 			  template_dir, hash_algo, ref_storage_format_uri,
-			  init_shared_repository, &reinit);
+			  object_storage_format_uri, init_shared_repository, &reinit);
 	create_reference_database(the_repository, initial_branch, quiet);
 	create_object_database(the_repository, NULL);
 
diff --git a/refs.c b/refs.c
index 951db56113..3f7458332e 100644
--- a/refs.c
+++ b/refs.c
@@ -3521,7 +3521,8 @@ int repo_migrate_ref_storage_format(struct repository *repo,
 	 * repository format so that clients will use the new ref store.
 	 * We also need to swap out the repository's main ref store.
 	 */
-	initialize_repository_version(repo, hash_algo_by_ptr(repo->hash_algo), format, 1);
+	initialize_repository_version(repo, hash_algo_by_ptr(repo->hash_algo), format,
+				      repo->object_storage_format, 1);
 
 	/*
 	 * Unset the old ref store and release it. `get_main_ref_store()` will
diff --git a/setup.c b/setup.c
index 70aa90a9cb..4a70977576 100644
--- a/setup.c
+++ b/setup.c
@@ -2459,6 +2459,7 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree)
 void initialize_repository_version(struct repository *repo,
 				   int hash_algo,
 				   enum ref_storage_format ref_storage_format,
+				   enum object_storage_format object_storage_format,
 				   int reinit)
 {
 	struct strbuf repo_version = STRBUF_INIT;
@@ -2474,7 +2475,9 @@ void initialize_repository_version(struct repository *repo,
 	 */
 	if (hash_algo != GIT_HASH_SHA1_LEGACY ||
 	    ref_storage_format != REF_STORAGE_FORMAT_FILES ||
-	    repo->ref_storage_payload)
+	    repo->ref_storage_payload ||
+	    object_storage_format != OBJECT_STORAGE_FORMAT_FILES ||
+	    repo->object_storage_payload)
 		target_version = GIT_REPO_VERSION_READ;
 
 	if (hash_algo != GIT_HASH_SHA1_LEGACY && hash_algo != GIT_HASH_UNKNOWN)
@@ -2498,6 +2501,22 @@ void initialize_repository_version(struct repository *repo,
 		repo_config_set_gently(repo, "extensions.refstorage", NULL);
 	}
 
+	if (repo->object_storage_payload) {
+		struct strbuf uri = STRBUF_INIT;
+
+		strbuf_addf(&uri, "%s://%s",
+			    object_storage_format_to_name(object_storage_format),
+			    repo->object_storage_payload);
+		repo_config_set(repo, "extensions.objectstorage", uri.buf);
+
+		strbuf_release(&uri);
+	} else if (object_storage_format != OBJECT_STORAGE_FORMAT_FILES) {
+		repo_config_set(repo, "extensions.objectstorage",
+				object_storage_format_to_name(object_storage_format));
+	} else if (reinit) {
+		repo_config_set_gently(repo, "extensions.objectstorage", NULL);
+	}
+
 	if (reinit) {
 		struct strbuf config = STRBUF_INIT;
 		struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
@@ -2624,7 +2643,8 @@ static int create_default_files(struct repository *repo,
 		adjust_shared_perm(repo, repo_get_git_dir(repo));
 	}
 
-	initialize_repository_version(repo, fmt->hash_algo, fmt->ref_storage_format, reinit);
+	initialize_repository_version(repo, fmt->hash_algo, fmt->ref_storage_format,
+				      fmt->object_storage_format, reinit);
 
 	/* Check filemode trustability */
 	repo_git_path_replace(repo, &path, "config");
@@ -2734,6 +2754,7 @@ static void separate_git_dir(struct repository *repo,
 struct default_format_config {
 	int hash;
 	enum ref_storage_format ref_storage_format;
+	enum object_storage_format object_storage_format;
 };
 
 static int read_default_format_config(const char *key, const char *value,
@@ -2765,6 +2786,16 @@ static int read_default_format_config(const char *key, const char *value,
 		goto out;
 	}
 
+	if (!strcmp(key, "init.defaultobjectstorageformat")) {
+		ret = git_config_string(&str, key, value);
+		if (ret)
+			goto out;
+		cfg->object_storage_format = object_storage_format_by_name(str);
+		if (cfg->object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
+			warning(_("unknown object storage format '%s'"), str);
+		goto out;
+	}
+
 	/*
 	 * Enable the reftable format when "features.experimental" is enabled.
 	 * "init.defaultRefStorageFormat" takes precedence over this setting.
@@ -2785,7 +2816,8 @@ static int read_default_format_config(const char *key, const char *value,
 
 static void repository_format_configure(struct repository_format *repo_fmt,
 					int hash,
-					const char *ref_storage_format_uri)
+					const char *ref_storage_format_uri,
+					const char *object_storage_uri)
 {
 	struct default_format_config cfg = {
 		.hash = GIT_HASH_UNKNOWN,
@@ -2796,7 +2828,9 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 		.ignore_repo = 1,
 		.ignore_worktree = 1,
 	};
+	enum object_storage_format object_storage_format;
 	enum ref_storage_format ref_storage_format;
+	char *object_storage_payload = NULL;
 	char *ref_storage_payload = NULL;
 	const char *env;
 
@@ -2885,9 +2919,52 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 		ref_storage_format = REF_STORAGE_FORMAT_DEFAULT;
 	}
 
+	/*
+	 * Determine the object storage format, with the same precedence as
+	 * for the ref storage format:
+	 *
+	 *   1. The format specified via the command line.
+	 *
+	 *   2. The format specified via "GIT_OBJECT_STORAGE_FORMAT".
+	 *
+	 *   3. The format of a preexisting repository when reinitializing.
+	 *
+	 *   4. The default format for new repositories as configured via
+	 *      "GIT_DEFAULT_OBJECT_STORAGE_FORMAT".
+	 *
+	 *   5. The default format for new repositories as configured via
+	 *      "init.defaultObjectStorageFormat".
+	 *
+	 *   6. The "files" format.
+	 */
+	if (object_storage_uri) {
+		object_storage_format = object_storage_format_by_uri(object_storage_uri,
+								     &object_storage_payload);
+		if (object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
+			die(_("unknown object storage format specified via command line: '%s'"),
+			    object_storage_uri);
+	} else if ((env = getenv(GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT))) {
+		object_storage_format = object_storage_format_by_uri(env, &object_storage_payload);
+		if (object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
+			die(_("unknown object storage format specified via %s: '%s'"),
+			    GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT, env);
+	} else if (repo_fmt->version >= 0) {
+		object_storage_format = repo_fmt->object_storage_format;
+		object_storage_payload = xstrdup_or_null(repo_fmt->object_storage_payload);
+	} else if ((env = getenv("GIT_DEFAULT_OBJECT_STORAGE_FORMAT"))) {
+		object_storage_format = object_storage_format_by_name(env);
+		if (object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
+			die(_("unknown object storage format specified via %s: '%s'"),
+			    "GIT_DEFAULT_OBJECT_STORAGE_FORMAT", env);
+	} else if (cfg.object_storage_format != OBJECT_STORAGE_FORMAT_UNKNOWN) {
+		object_storage_format = cfg.object_storage_format;
+	} else {
+		object_storage_format = OBJECT_STORAGE_FORMAT_FILES;
+	}
+
 	/*
 	 * If we have a preexisting repository we need to verify that its
-	 * current ref storage format does not change.
+	 * current storage formats do not change.
 	 */
 	if (repo_fmt->version >= 0) {
 		if (ref_storage_format != repo_fmt->ref_storage_format)
@@ -2896,11 +2973,21 @@ static void repository_format_configure(struct repository_format *repo_fmt,
 		    strcmp(ref_storage_payload ? ref_storage_payload : "",
 			   repo_fmt->ref_storage_payload ? repo_fmt->ref_storage_payload : ""))
 			die(_("attempt to reinitialize repository with different reference storage payload"));
+		if (object_storage_format != repo_fmt->object_storage_format)
+			die(_("attempt to reinitialize repository with different object storage format"));
+		if ((object_storage_payload || repo_fmt->object_storage_payload) &&
+		    strcmp(object_storage_payload ? object_storage_payload : "",
+			   repo_fmt->object_storage_payload ? repo_fmt->object_storage_payload : ""))
+			die(_("attempt to reinitialize repository with different object storage payload"));
 	}
 
 	free(repo_fmt->ref_storage_payload);
 	repo_fmt->ref_storage_format = ref_storage_format;
 	repo_fmt->ref_storage_payload = ref_storage_payload;
+
+	free(repo_fmt->object_storage_payload);
+	repo_fmt->object_storage_format = object_storage_format;
+	repo_fmt->object_storage_payload = object_storage_payload;
 }
 
 void create_repository(struct repository *repo,
@@ -2910,6 +2997,7 @@ void create_repository(struct repository *repo,
 		       const char *template_dir,
 		       int hash,
 		       const char *ref_storage_format_uri,
+		       const char *object_storage_format_uri,
 		       int init_shared_repository,
 		       int *reinit_ok)
 {
@@ -2948,7 +3036,7 @@ void create_repository(struct repository *repo,
 	 * is an attempt to reinitialize new repository with an old tool.
 	 */
 	read_and_verify_repository_format(&repo_fmt, repo_get_git_dir(repo), NULL);
-	repository_format_configure(&repo_fmt, hash, ref_storage_format_uri);
+	repository_format_configure(&repo_fmt, hash, ref_storage_format_uri, object_storage_format_uri);
 	if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)
 		die("%s", err.buf);
 
diff --git a/setup.h b/setup.h
index a4afa8910e..829b63228e 100644
--- a/setup.h
+++ b/setup.h
@@ -277,12 +277,14 @@ void create_repository(struct repository *repo,
 		       const char *template_dir,
 		       int hash_algo,
 		       const char *ref_storage_format_uri,
+		       const char *object_storage_format_uri,
 		       int init_shared_repository,
 		       int *reinit_ok);
 
 void initialize_repository_version(struct repository *repo,
 				   int hash_algo,
 				   enum ref_storage_format ref_storage_format,
+				   enum object_storage_format object_storage_format,
 				   int reinit);
 
 /*
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index d080ab04a8..23317e0733 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -898,6 +898,172 @@ test_expect_success DEFAULT_REPO_FORMAT 'GIT_OBJECT_STORAGE_FORMAT overrides ext
 	)
 '
 
+test_expect_success 'init with --object-storage-format=files does not set extension' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files objformat &&
+	test_must_fail git -C objformat config get extensions.objectstorage &&
+	echo files >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual &&
+	if test_have_prereq DEFAULT_REPO_FORMAT
+	then
+		echo 0 >expect &&
+		git -C objformat config get core.repositoryformatversion >actual &&
+		test_cmp expect actual
+	fi
+'
+
+test_expect_success 'init with --object-storage-format=files:// does not set extension' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files:// objformat &&
+	test_must_fail git -C objformat config get extensions.objectstorage &&
+	echo files >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'init with --object-storage-format=files://<relative-path>' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files://objs objformat &&
+	echo files://objs >expect &&
+	git -C objformat config get extensions.objectstorage >actual &&
+	test_cmp expect actual &&
+	echo 1 >expect &&
+	git -C objformat config get core.repositoryformatversion >actual &&
+	test_cmp expect actual &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	echo files://objs >expect &&
+	test_cmp expect actual &&
+	test_path_is_dir objformat/.git/objects &&
+	test_path_is_dir objformat/.git/objs &&
+	test_commit -C objformat A &&
+	git -C objformat rev-parse --verify HEAD &&
+	test_path_is_missing objformat/.git/objects/pack &&
+	test_path_is_dir objformat/.git/objs/pack
+'
+
+test_expect_success 'init with --object-storage-format=files://<absolute-path>' '
+	test_when_finished "rm -rf objformat objdir" &&
+	git init --object-storage-format=files://"$(pwd)/objdir" objformat &&
+	echo "files://$(pwd)/objdir" >expect &&
+	git -C objformat config get extensions.objectstorage >actual &&
+	test_cmp expect actual &&
+	test_commit -C objformat A &&
+	git -C objformat rev-parse --verify HEAD &&
+	test_path_is_dir objdir/pack
+'
+
+test_expect_success 'GIT_OBJECT_DIRECTORY takes precedence over extensions.objectStorage payload' '
+	test_when_finished "rm -rf objformat objdir" &&
+	git init --object-storage-format=files://objs objformat &&
+	test_commit -C objformat A &&
+	mkdir objdir &&
+	test_must_fail env GIT_DIR="$PWD/objformat/.git" GIT_OBJECT_DIRECTORY="$PWD/objdir" \
+		git cat-file -e HEAD &&
+	GIT_DIR="$PWD/objformat/.git" GIT_OBJECT_DIRECTORY="$PWD/objdir" \
+		git hash-object -w objformat/A.t >oid &&
+	test_path_is_file objdir/$(test_oid_to_path "$(cat oid)")
+'
+
+test_expect_success 'init with --object-storage-format=garbage' '
+	test_when_finished "rm -rf objformat" &&
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format specified via command line: ${SQ}garbage${SQ}
+	EOF
+	test_must_fail git init --object-storage-format=garbage objformat 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'init with GIT_DEFAULT_OBJECT_STORAGE_FORMAT=garbage' '
+	test_when_finished "rm -rf objformat" &&
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format specified via GIT_DEFAULT_OBJECT_STORAGE_FORMAT: ${SQ}garbage${SQ}
+	EOF
+	test_must_fail env GIT_DEFAULT_OBJECT_STORAGE_FORMAT=garbage git init objformat 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'init with GIT_OBJECT_STORAGE_FORMAT=garbage' '
+	test_when_finished "rm -rf objformat" &&
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format specified via GIT_OBJECT_STORAGE_FORMAT: ${SQ}garbage${SQ}
+	EOF
+	test_must_fail env GIT_OBJECT_STORAGE_FORMAT=garbage git init objformat 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'GIT_OBJECT_STORAGE_FORMAT takes precedence over default' '
+	test_when_finished "rm -rf objformat" &&
+	GIT_DEFAULT_OBJECT_STORAGE_FORMAT=files://default \
+	GIT_OBJECT_STORAGE_FORMAT=files://explicit git init objformat &&
+	echo files://explicit >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success '--object-storage-format= takes precedence over environment' '
+	test_when_finished "rm -rf objformat" &&
+	GIT_OBJECT_STORAGE_FORMAT=files://explicit \
+	git init --object-storage-format=files://cmdline objformat &&
+	echo files://cmdline >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 're-init refuses with different storage format' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files objformat &&
+	cat >expect <<-EOF &&
+	fatal: attempt to reinitialize repository with different object storage payload
+	EOF
+	test_must_fail env GIT_OBJECT_STORAGE_FORMAT=files://objs git init objformat 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 're-init with same object storage format' '
+	test_when_finished "rm -rf objformat" &&
+	git init --object-storage-format=files://objs objformat &&
+	git init --object-storage-format=files://objs objformat &&
+	git init objformat &&
+	echo files://objs >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success DEFAULT_REPO_FORMAT 're-init with default object storage format drops extension' '
+	test_when_finished "rm -rf objformat" &&
+
+	git init objformat &&
+	git -C objformat config set core.repositoryformatversion 1 &&
+	git -C objformat config set extensions.objectstorage files &&
+
+	git init --object-storage-format=files objformat &&
+	echo 0 >expect &&
+	git -C objformat config get core.repositoryformatversion >actual &&
+	test_cmp expect actual &&
+	test_must_fail git -C objformat config get extensions.objectstorage
+'
+
+test_expect_success 'init warns about invalid init.defaultObjectStorageFormat' '
+	test_when_finished "rm -rf objformat" &&
+	test_config_global init.defaultObjectStorageFormat garbage &&
+	echo "warning: unknown object storage format ${SQ}garbage${SQ}" >expect &&
+	git init objformat 2>err &&
+	test_cmp expect err &&
+	echo files >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'init with init.defaultObjectStorageFormat' '
+	test_when_finished "rm -rf objformat" &&
+	test_config_global init.defaultObjectStorageFormat files &&
+	git init objformat &&
+	echo files >expect &&
+	git -C objformat rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success MINGW 'core.hidedotfiles = false' '
 	git config --global core.hidedotfiles false &&
 	rm -rf newdir &&

-- 
2.56.0.307.g5fe7bd680c.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v3 6/7] builtin/clone: introduce "--object-storage-format="
  2026-10-23 11:49 ` [PATCH v3 " Patrick Steinhardt
                     ` (4 preceding siblings ...)
  2026-10-23 11:49   ` [PATCH v3 5/7] builtin/init: introduce handling of " Patrick Steinhardt
@ 2026-10-23 11:49   ` Patrick Steinhardt
  2026-10-23 11:49   ` [PATCH v3 7/7] builtin/submodule: propagate "--object-storage-format=" Patrick Steinhardt
  2026-10-26 11:35   ` [PATCH v3 0/7] odb: introduce object storage extension Karthik Nayak
  7 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-23 11:49 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

Wire up the new object storage format in git-clone(1) via a new
`--object-storage-format=` option, same as we have introduced in the
preceding commit for git-init(1).

Note that "--local" clones will not work if either of the object
databases is not using the "files" format. This is because we hardlink
loose objects and packfiles into the new repository, and that is
something that simply won't work with alternative backends.
Consequently, we either downgrade to a non-local clone with a warning,
or we die in case the user has explicitly asked for a local clone.

We _could_ in theory handle the case where the "files" backend has been
configured with an alternative object directory path (that is, with
"files:///path/to/dir"). But that's left for a future patch series to
implement for the sake of simplicity.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-clone.adoc | 11 +++++++
 builtin/clone.c              | 45 +++++++++++++++++++++++++++--
 t/t5601-clone.sh             | 68 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 122 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-clone.adoc b/Documentation/git-clone.adoc
index 07f7cf1a98..dd3b44817a 100644
--- a/Documentation/git-clone.adoc
+++ b/Documentation/git-clone.adoc
@@ -360,6 +360,17 @@ include::ref-storage-format.adoc[]
 `--ref-format=<format>`::
 Deprecated alias of `--ref-storage-format=<format>`.
 
+`--object-storage-format=<format>`::
+Specify the given object storage _<format>_ for the repository. Backends that
+require additional configuration accept a payload in the form
+`<format>://<payload>`, for example a path or a connection string identifying
+the database that shall store the objects. The valid values are:
++
+include::object-storage-format.adoc[]
++
+Local clones (see `--local`) fall back to the regular transport when either
+repository uses a non-default object storage.
+
 `-j<n>`::
 `--jobs=<n>`::
 	The number of submodules fetched at the same time.
diff --git a/builtin/clone.c b/builtin/clone.c
index 95e641b1a4..437dd2b277 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -378,6 +378,24 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
 	dir_iterator_free(iter);
 }
 
+static bool is_using_default_object_storage_format(const char *repo_path)
+{
+	struct repository_format format = REPOSITORY_FORMAT_INIT;
+	struct strbuf config = STRBUF_INIT;
+	bool is_default;
+
+	get_common_dir(&config, repo_path);
+	strbuf_addstr(&config, "/config");
+	read_repository_format(&format, config.buf);
+
+	is_default = format.object_storage_format == OBJECT_STORAGE_FORMAT_FILES &&
+		!format.object_storage_payload;
+
+	clear_repository_format(&format);
+	strbuf_release(&config);
+	return is_default;
+}
+
 static void clone_local(const char *src_repo, const char *dest_repo)
 {
 	if (!option_shared) {
@@ -927,6 +945,7 @@ int cmd_clone(int argc,
 	struct string_list option_not = STRING_LIST_INIT_NODUP;
 	const char *real_git_dir = NULL;
 	const char *ref_storage_format_uri = NULL;
+	const char *object_storage_format_uri = NULL;
 	const char *option_upload_pack = "git-upload-pack";
 	int option_progress = -1;
 	int option_sparse_checkout = 0;
@@ -1008,6 +1027,8 @@ int cmd_clone(int argc,
 			   N_("separate git dir from working tree")),
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format_uri, N_("format"),
 			   N_("specify the reference storage format to use")),
+		OPT_STRING(0, "object-storage-format", &object_storage_format_uri, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
 		OPT_STRING_LIST('c', "config", &option_config, N_("key=value"),
 				N_("set config inside the new repository")),
@@ -1060,6 +1081,10 @@ int cmd_clone(int argc,
 			die(_("unknown ref storage format '%s'"), ref_storage_format_uri);
 	}
 
+	if (object_storage_format_uri &&
+	    object_storage_format_by_uri(object_storage_format_uri, NULL) == OBJECT_STORAGE_FORMAT_UNKNOWN)
+		die(_("unknown object storage format '%s'"), object_storage_format_uri);
+
 	if (option_mirror) {
 		option_bare = 1;
 		option_tags = 0;
@@ -1213,8 +1238,9 @@ int cmd_clone(int argc,
 	 * their on-disk data structures.
 	 */
 	create_repository(the_repository, git_dir, real_git_dir, work_tree,
-			  option_template, GIT_HASH_UNKNOWN, ref_storage_format_uri,
-			  NULL, do_not_override_repo_unix_permissions, NULL);
+			  option_template, GIT_HASH_UNKNOWN,
+			  ref_storage_format_uri, object_storage_format_uri,
+			  do_not_override_repo_unix_permissions, NULL);
 
 	if (real_git_dir) {
 		free((char *)git_dir);
@@ -1363,6 +1389,21 @@ int cmd_clone(int argc,
 			is_local = 0;
 		}
 	}
+
+	/*
+	 * Local clones copy or hardlink the object directory of the source repository
+	 * into the destination. This only works when both repositories use the "files"
+	 * object storage format.
+	 */
+	if (is_local &&
+	    (!is_using_default_object_storage_format(path) || !is_using_default_object_storage_format(git_dir))) {
+		if (option_local > 0)
+			die(_("cannot perform local clone with non-default object storage format"));
+		else
+			warning(_("falling back to non-local clone because of non-default object storage format"));
+		is_local = 0;
+	}
+
 	if (option_local > 0 && !is_local)
 		warning(_("--local is ignored"));
 
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 06f6121f82..85f439d6b6 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -181,6 +181,74 @@ test_expect_success 'clone with garbage ref format' '
 	test_path_is_missing ref-storage
 '
 
+test_expect_success 'clone with files object storage format' '
+	test_when_finished "rm -rf object-storage" &&
+	git clone --object-storage-format=files --bare src object-storage &&
+	test_must_fail git -C object-storage config get extensions.objectstorage &&
+	git -C object-storage rev-parse --show-object-storage-format >actual &&
+	echo files >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'clone with object storage payload' '
+	test_when_finished "rm -rf object-storage" &&
+	git clone --object-storage-format=files://objs --bare src object-storage &&
+	git -C object-storage config get extensions.objectstorage >actual &&
+	echo files://objs >expect &&
+	test_cmp expect actual &&
+	git -C object-storage rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual &&
+	test_path_is_dir object-storage/objs/pack &&
+	test_path_is_missing object-storage/objects/pack
+'
+
+test_expect_success 'clone with object storage payload and bundle URI' '
+	test_when_finished "rm -rf object-storage src.bundle" &&
+	git -C src bundle create ../src.bundle --all &&
+	git clone --object-storage-format=files://objs --bundle-uri="$PWD/src.bundle" \
+		src object-storage &&
+	git -C object-storage rev-parse --show-object-storage-format >actual &&
+	echo files://objs >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'clone with garbage object storage format' '
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format ${SQ}garbage${SQ}
+	EOF
+	test_must_fail git clone --object-storage-format=garbage --bare src object-storage 2>err &&
+	test_cmp expect err &&
+	test_path_is_missing object-storage
+'
+
+test_expect_success 'local clone into non-default object storage bails' '
+	test_when_finished "rm -rf object-storage" &&
+	test_must_fail git clone --local --object-storage-format=files://objs src object-storage 2>err &&
+	test_grep "cannot perform local clone with non-default object storage format" err
+'
+
+test_expect_success 'local clone from explicit files object storage succeeds' '
+	test_when_finished "rm -rf object-storage object-storage-dest" &&
+	git clone src object-storage &&
+	git -C object-storage config set core.repositoryformatversion 1 &&
+	git -C object-storage config set extensions.objectstorage files &&
+	git clone --local object-storage object-storage-dest
+'
+
+test_expect_success 'local clone from non-default object storage bails' '
+	test_when_finished "rm -rf object-storage object-storage-dest" &&
+	git clone --object-storage-format=files://objs src object-storage &&
+	test_must_fail git clone --local object-storage object-storage-dest 2>err &&
+	test_grep "cannot perform local clone with non-default object storage format" err
+'
+
+test_expect_success 'local clone from non-default object storage without --local warns' '
+	test_when_finished "rm -rf object-storage object-storage-dest" &&
+	git clone --object-storage-format=files://objs src object-storage &&
+	git clone object-storage object-storage-dest 2>err &&
+	test_grep "falling back to non-local clone" err
+'
+
 test_expect_success 'clone to destination with trailing /' '
 
 	git clone src target-1/ &&

-- 
2.56.0.307.g5fe7bd680c.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* [PATCH v3 7/7] builtin/submodule: propagate "--object-storage-format="
  2026-10-23 11:49 ` [PATCH v3 " Patrick Steinhardt
                     ` (5 preceding siblings ...)
  2026-10-23 11:49   ` [PATCH v3 6/7] builtin/clone: introduce "--object-storage-format=" Patrick Steinhardt
@ 2026-10-23 11:49   ` Patrick Steinhardt
  2026-10-26 11:35   ` [PATCH v3 0/7] odb: introduce object storage extension Karthik Nayak
  7 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-23 11:49 UTC (permalink / raw)
  To: git; +Cc: Karthik Nayak

The ref storage format passed to git-clone(1) propagates to recursively
cloned submodules, and git-submodule(1)'s "add" and "update" subcommands
accept `--ref-storage-format=` so that newly cloned submodules can be
set up with a specific format.

Teach git-submodule(1) to also propagate the object storage format.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-submodule.adoc           |  10 ++-
 builtin/clone.c                            |  19 ++++--
 builtin/submodule--helper.c                |  40 +++++++++++
 git-submodule.sh                           |  18 +++++
 t/meson.build                              |   1 +
 t/t7427-submodule-object-storage-format.sh | 104 +++++++++++++++++++++++++++++
 6 files changed, 185 insertions(+), 7 deletions(-)

diff --git a/Documentation/git-submodule.adoc b/Documentation/git-submodule.adoc
index e9c81a7170..57a21bc8d9 100644
--- a/Documentation/git-submodule.adoc
+++ b/Documentation/git-submodule.adoc
@@ -34,7 +34,7 @@ COMMANDS
 With no arguments, shows the status of existing submodules.  Several
 subcommands are available to perform operations on the submodules.
 
-`add [-b <branch>] [-f | --force] [--name <name>] [--reference <repository>] [--ref-storage-format <format>] [--depth <depth>] [--] <repository> [<path>]`::
+`add [-b <branch>] [-f | --force] [--name <name>] [--reference <repository>] [--ref-storage-format <format>] [--object-storage-format <format>] [--depth <depth>] [--] <repository> [<path>]`::
 	Add the given repository as a submodule at the given path
 	to the changeset to be committed next to the current
 	project: the current project is termed the "superproject".
@@ -75,6 +75,9 @@ URL in `.gitmodules`.
 If `--ref-storage-format <format>` or its deprecated alias `--ref-format
 <format>` is specified, the ref storage format of the newly cloned submodules
 will be set accordingly.
++
+If `--object-storage-format <format>` is specified, the object storage format
+of the newly cloned submodules will be set accordingly.
 
 `status [--cached] [--recursive] [--] [<path>...]`::
 	Show the status of the submodules. This will print the SHA-1 of the
@@ -140,7 +143,7 @@ If you really want to remove a submodule from the repository and commit
 that use linkgit:git-rm[1] instead. See linkgit:gitsubmodules[7] for removal
 options.
 
-`update [--init] [--remote] [-N | --no-fetch] [--[no-]recommend-shallow] [-f | --force] [--checkout | --rebase | --merge] [--reference=<repository>] [--ref-storage-format=<format>] [--depth=<depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter=<filter-spec>] [--] [<path>...]`::
+`update [--init] [--remote] [-N | --no-fetch] [--[no-]recommend-shallow] [-f | --force] [--checkout | --rebase | --merge] [--reference=<repository>] [--ref-storage-format=<format>] [--object-storage-format=<format>] [--depth=<depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter=<filter-spec>] [--] [<path>...]`::
 +
 --
 Update the registered submodules to match what the superproject
@@ -193,6 +196,9 @@ If `--ref-storage-format <format>` or its deprecated alias `--ref-format
 <format>` is specified, the ref storage format of the newly cloned submodules
 will be set accordingly.
 
+If `--object-storage-format <format>` is specified, the object storage format
+of the newly cloned submodules will be set accordingly.
+
 If `--filter <filter-spec>` is specified, the given partial clone filter will be
 applied to the submodule. See linkgit:git-rev-list[1] for details on filter
 specifications.
diff --git a/builtin/clone.c b/builtin/clone.c
index 437dd2b277..993a4390b8 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -681,7 +681,8 @@ static int git_sparse_checkout_init(const char *repo)
 static int checkout(int submodule_progress,
 		    struct list_objects_filter_options *filter_options,
 		    int filter_submodules,
-		    enum ref_storage_format ref_storage_format)
+		    enum ref_storage_format ref_storage_format,
+		    enum object_storage_format object_storage_format)
 {
 	struct object_id oid;
 	char *head;
@@ -771,6 +772,10 @@ static int checkout(int submodule_progress,
 			strvec_pushf(&cmd.args, "--ref-storage-format=%s",
 				     ref_storage_format_to_name(ref_storage_format));
 
+		if (object_storage_format != OBJECT_STORAGE_FORMAT_UNKNOWN)
+			strvec_pushf(&cmd.args, "--object-storage-format=%s",
+				     object_storage_format_to_name(object_storage_format));
+
 		if (filter_submodules && filter_options->choice)
 			strvec_pushf(&cmd.args, "--filter=%s",
 				     expand_list_objects_filter_spec(filter_options));
@@ -936,6 +941,7 @@ int cmd_clone(int argc,
 	int submodule_progress;
 	int filter_submodules = 0;
 	int hash_algo;
+	enum object_storage_format object_storage_format = OBJECT_STORAGE_FORMAT_UNKNOWN;
 	enum ref_storage_format ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN;
 	const int do_not_override_repo_unix_permissions = -1;
 	int option_reject_shallow = -1; /* unspecified */
@@ -1081,9 +1087,11 @@ int cmd_clone(int argc,
 			die(_("unknown ref storage format '%s'"), ref_storage_format_uri);
 	}
 
-	if (object_storage_format_uri &&
-	    object_storage_format_by_uri(object_storage_format_uri, NULL) == OBJECT_STORAGE_FORMAT_UNKNOWN)
-		die(_("unknown object storage format '%s'"), object_storage_format_uri);
+	if (object_storage_format_uri) {
+		object_storage_format = object_storage_format_by_uri(object_storage_format_uri, NULL);
+		if (object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
+			die(_("unknown object storage format '%s'"), object_storage_format_uri);
+	}
 
 	if (option_mirror) {
 		option_bare = 1;
@@ -1701,7 +1709,8 @@ int cmd_clone(int argc,
 	err = checkout(submodule_progress,
 		       &filter_options,
 		       filter_submodules,
-		       ref_storage_format);
+		       ref_storage_format,
+		       object_storage_format);
 
 	list_objects_filter_release(&filter_options);
 
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 64412adf9f..461b682d0d 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1739,6 +1739,7 @@ struct module_clone_data {
 	int depth;
 	struct list_objects_filter_options *filter_options;
 	enum ref_storage_format ref_storage_format;
+	enum object_storage_format object_storage_format;
 	unsigned int quiet: 1;
 	unsigned int progress: 1;
 	unsigned int dissociate: 1;
@@ -1748,6 +1749,7 @@ struct module_clone_data {
 #define MODULE_CLONE_DATA_INIT { \
 	.single_branch = -1, \
 	.ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN, \
+	.object_storage_format = OBJECT_STORAGE_FORMAT_UNKNOWN, \
 }
 
 struct submodule_alternate_setup {
@@ -1948,6 +1950,9 @@ static int clone_submodule(const struct module_clone_data *clone_data,
 		if (clone_data->ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
 			strvec_pushf(&cp.args, "--ref-storage-format=%s",
 				     ref_storage_format_to_name(clone_data->ref_storage_format));
+		if (clone_data->object_storage_format != OBJECT_STORAGE_FORMAT_UNKNOWN)
+			strvec_pushf(&cp.args, "--object-storage-format=%s",
+				     object_storage_format_to_name(clone_data->object_storage_format));
 		if (clone_data->dissociate)
 			strvec_push(&cp.args, "--dissociate");
 		if (sm_gitdir && *sm_gitdir)
@@ -2044,6 +2049,7 @@ static int module_clone(int argc, const char **argv, const char *prefix,
 	struct string_list reference = STRING_LIST_INIT_NODUP;
 	struct list_objects_filter_options filter_options =
 		LIST_OBJECTS_FILTER_INIT;
+	const char *object_storage_format = NULL;
 	const char *ref_storage_format = NULL;
 
 	struct option module_clone_options[] = {
@@ -2065,6 +2071,8 @@ static int module_clone(int argc, const char **argv, const char *prefix,
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
 			   N_("specify the reference storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
+		OPT_STRING(0, "object-storage-format", &object_storage_format, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_BOOL(0, "dissociate", &dissociate,
 			   N_("use --reference only while cloning")),
 		OPT_INTEGER(0, "depth", &clone_data.depth,
@@ -2095,6 +2103,13 @@ static int module_clone(int argc, const char **argv, const char *prefix,
 		if (clone_data.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
 			die(_("unknown ref storage format '%s'"), ref_storage_format);
 	}
+
+	if (object_storage_format) {
+		clone_data.object_storage_format = object_storage_format_by_name(object_storage_format);
+		if (clone_data.object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
+			die(_("unknown object storage format '%s'"), object_storage_format);
+	}
+
 	clone_data.dissociate = !!dissociate;
 	clone_data.quiet = !!quiet;
 	clone_data.progress = !!progress;
@@ -2201,6 +2216,7 @@ struct update_data {
 	struct submodule_update_strategy update_strategy;
 	struct list_objects_filter_options *filter_options;
 	struct module_list list;
+	enum object_storage_format object_storage_format;
 	enum ref_storage_format ref_storage_format;
 	int depth;
 	int max_jobs;
@@ -2365,6 +2381,9 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
 	if (suc->update_data->ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
 		strvec_pushf(&child->args, "--ref-storage-format=%s",
 			     ref_storage_format_to_name(suc->update_data->ref_storage_format));
+	if (suc->update_data->object_storage_format != OBJECT_STORAGE_FORMAT_UNKNOWN)
+		strvec_pushf(&child->args, "--object-storage-format=%s",
+			     object_storage_format_to_name(suc->update_data->object_storage_format));
 	strvec_pushl(&child->args, "--path", sub->path, NULL);
 	strvec_pushl(&child->args, "--name", sub->name, NULL);
 	strvec_pushl(&child->args, "--url", url, NULL);
@@ -2809,6 +2828,9 @@ static void update_data_to_args(const struct update_data *update_data,
 	if (update_data->ref_storage_format != REF_STORAGE_FORMAT_UNKNOWN)
 		strvec_pushf(args, "--ref-storage-format=%s",
 			     ref_storage_format_to_name(update_data->ref_storage_format));
+	if (update_data->object_storage_format)
+		strvec_pushf(args, "--object-storage-format=%s",
+			     object_storage_format_to_name(update_data->object_storage_format));
 	if (update_data->filter_options && update_data->filter_options->choice)
 		strvec_pushf(args, "--filter=%s",
 				expand_list_objects_filter_spec(
@@ -2992,6 +3014,7 @@ static int module_update(int argc, const char **argv, const char *prefix,
 	struct update_data opt = UPDATE_DATA_INIT;
 	struct list_objects_filter_options filter_options =
 		LIST_OBJECTS_FILTER_INIT;
+	const char *object_storage_format = NULL;
 	const char *ref_storage_format = NULL;
 	int ret;
 	struct option module_update_options[] = {
@@ -3019,6 +3042,8 @@ static int module_update(int argc, const char **argv, const char *prefix,
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
 			   N_("specify the reference storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
+		OPT_STRING(0, "object-storage-format", &object_storage_format, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_BOOL(0, "dissociate", &opt.dissociate,
 			   N_("use --reference only while cloning")),
 		OPT_INTEGER(0, "depth", &opt.depth,
@@ -3067,6 +3092,11 @@ static int module_update(int argc, const char **argv, const char *prefix,
 		if (opt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
 			die(_("unknown ref storage format '%s'"), ref_storage_format);
 	}
+	if (object_storage_format) {
+		opt.object_storage_format = object_storage_format_by_name(object_storage_format);
+		if (opt.object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
+			die(_("unknown object storage format '%s'"), object_storage_format);
+	}
 
 	opt.filter_options = &filter_options;
 	opt.prefix = prefix;
@@ -3371,6 +3401,7 @@ struct add_data {
 	const char *sm_name;
 	const char *repo;
 	const char *realrepo;
+	enum object_storage_format object_storage_format;
 	enum ref_storage_format ref_storage_format;
 	int depth;
 	unsigned int force: 1;
@@ -3477,6 +3508,7 @@ static int add_submodule(const struct add_data *add_data)
 			string_list_append(&reference, p)->util = p;
 		}
 		clone_data.ref_storage_format = add_data->ref_storage_format;
+		clone_data.object_storage_format = add_data->object_storage_format;
 		clone_data.dissociate = add_data->dissociate;
 		if (add_data->depth >= 0)
 			clone_data.depth = add_data->depth;
@@ -3652,6 +3684,7 @@ static int module_add(int argc, const char **argv, const char *prefix,
 {
 	int force = 0, quiet = 0, progress = 0, dissociate = 0;
 	struct add_data add_data = ADD_DATA_INIT;
+	const char *object_storage_format = NULL;
 	const char *ref_storage_format = NULL;
 	char *to_free = NULL;
 	const struct submodule *existing;
@@ -3669,6 +3702,8 @@ static int module_add(int argc, const char **argv, const char *prefix,
 		OPT_STRING(0, "ref-storage-format", &ref_storage_format, N_("format"),
 			   N_("specify the reference storage format to use")),
 		OPT_ALIAS_F(0, "ref-format", "ref-storage-format", PARSE_OPT_HIDDEN),
+		OPT_STRING(0, "object-storage-format", &object_storage_format, N_("format"),
+			   N_("specify the object storage format to use")),
 		OPT_BOOL(0, "dissociate", &dissociate, N_("borrow the objects from reference repositories")),
 		OPT_STRING(0, "name", &add_data.sm_name, N_("name"),
 			   N_("sets the submodule's name to the given string "
@@ -3700,6 +3735,11 @@ static int module_add(int argc, const char **argv, const char *prefix,
 		if (add_data.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
 			die(_("unknown ref storage format '%s'"), ref_storage_format);
 	}
+	if (object_storage_format) {
+		add_data.object_storage_format = object_storage_format_by_name(object_storage_format);
+		if (add_data.object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
+			die(_("unknown object storage format '%s'"), object_storage_format);
+	}
 
 	add_data.repo = argv[0];
 	if (argc == 1)
diff --git a/git-submodule.sh b/git-submodule.sh
index 8632194138..4de5ffba04 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -103,6 +103,14 @@ cmd_add()
 		--ref-format=*|--ref-storage-format=*)
 			ref_storage_format="$1"
 			;;
+		--object-storage-format)
+			case "$2" in '') usage ;; esac
+			object_storage_format="--object-storage-format=$2"
+			shift
+			;;
+		--object-storage-format=*)
+			object_storage_format="$1"
+			;;
 		--dissociate)
 			dissociate=$1
 			;;
@@ -148,6 +156,7 @@ cmd_add()
 		${branch:+"$branch"} \
 		${reference:+"$reference"} \
 		${ref_storage_format:+"$ref_storage_format"} \
+		${object_storage_format:+"$object_storage_format"} \
 		$dissociate \
 		${name:+"$name"} \
 		${depth:+"$depth"} \
@@ -310,6 +319,14 @@ cmd_update()
 		--ref-format=*|--ref-storage-format=*)
 			ref_storage_format="$1"
 			;;
+		--object-storage-format)
+			case "$2" in '') usage ;; esac
+			object_storage_format="--object-storage-format=$2"
+			shift
+			;;
+		--object-storage-format=*)
+			object_storage_format="$1"
+			;;
 		--reference)
 			case "$2" in '') usage ;; esac
 			reference="--reference=$2"
@@ -386,6 +403,7 @@ cmd_update()
 		$merge \
 		$checkout \
 		${ref_storage_format:+"$ref_storage_format"} \
+		${object_storage_format:+"$object_storage_format"} \
 		${reference:+"$reference"} \
 		$dissociate \
 		${depth:+"$depth"} \
diff --git a/t/meson.build b/t/meson.build
index 11b3752f00..8542035b6f 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -926,6 +926,7 @@ integration_tests = [
   't7424-submodule-mixed-ref-formats.sh',
   't7425-submodule-gitdir-path-extension.sh',
   't7426-submodule-get-default-remote.sh',
+  't7427-submodule-object-storage-format.sh',
   't7450-bad-git-dotfiles.sh',
   't7500-commit-template-squash-signoff.sh',
   't7501-commit-basic-functionality.sh',
diff --git a/t/t7427-submodule-object-storage-format.sh b/t/t7427-submodule-object-storage-format.sh
new file mode 100755
index 0000000000..e4e554b073
--- /dev/null
+++ b/t/t7427-submodule-object-storage-format.sh
@@ -0,0 +1,104 @@
+#!/bin/sh
+
+test_description='submodules handle the object storage format'
+
+. ./test-lib.sh
+
+test_object_storage_format () {
+	echo "$2" >expect &&
+	git -C "$1" rev-parse --show-object-storage-format >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'setup' '
+	git config set --global protocol.file.allow always
+'
+
+test_expect_success 'add existing repository with different object storage format' '
+	test_when_finished "rm -rf parent" &&
+
+	git init parent &&
+	(
+		cd parent &&
+		test_commit parent &&
+		git init --object-storage-format=files://objs submodule &&
+		test_commit -C submodule submodule &&
+		git submodule add ./submodule &&
+		test_object_storage_format submodule files://objs
+	)
+'
+
+test_expect_success 'add submodules with explicit files storage format' '
+	test_when_finished "rm -rf submodule upstream" &&
+
+	git init submodule &&
+	test_commit -C submodule submodule-initial &&
+	git init upstream &&
+	test_object_storage_format upstream files &&
+	git -C upstream submodule add --object-storage-format=files "file://$(pwd)/submodule" &&
+	test_object_storage_format upstream/submodule files
+'
+
+test_expect_success 'add submodules with garbage object storage format' '
+	test_when_finished "rm -rf submodule upstream" &&
+
+	git init submodule &&
+	test_commit -C submodule submodule-initial &&
+	git init upstream &&
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format ${SQ}garbage${SQ}
+	EOF
+	test_must_fail git -C upstream submodule add --object-storage-format=garbage "file://$(pwd)/submodule" 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'add submodules with object storage payload is rejected' '
+	test_when_finished "rm -rf submodule upstream" &&
+
+	git init submodule &&
+	test_commit -C submodule submodule-initial &&
+	git init upstream &&
+	cat >expect <<-EOF &&
+	fatal: unknown object storage format ${SQ}files://objs${SQ}
+	EOF
+	test_must_fail git -C upstream submodule add --object-storage-format=files://objs "file://$(pwd)/submodule" 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success 'recursive clone propagates object storage format' '
+	test_when_finished "rm -rf submodule upstream downstream" &&
+
+	git init submodule &&
+	test_commit -C submodule submodule-initial &&
+	git init upstream &&
+	git -C upstream submodule add "file://$(pwd)/submodule" &&
+	git -C upstream commit -am "add submodule" &&
+
+	test_object_storage_format upstream files &&
+	test_object_storage_format upstream/submodule files &&
+
+	git clone --object-storage-format=files://objs --recurse-submodules \
+		upstream downstream &&
+	test_object_storage_format downstream files://objs &&
+	test_object_storage_format downstream/submodule files &&
+	test_path_is_dir downstream/.git/objs/pack &&
+	test_path_is_dir downstream/.git/modules/submodule/objects/pack
+'
+
+test_expect_success 'clone submodules with explicit object storage format' '
+	test_when_finished "rm -rf submodule upstream downstream" &&
+
+	git init submodule &&
+	test_commit -C submodule submodule-initial &&
+	git init upstream &&
+	git -C upstream submodule add "file://$(pwd)/submodule" &&
+	git -C upstream commit -m "upstream submodule" &&
+
+	git clone --no-recurse-submodules "file://$(pwd)/upstream" downstream &&
+	test_object_storage_format downstream files &&
+	git -C downstream submodule update --init --object-storage-format=files &&
+	test_object_storage_format downstream/submodule files &&
+	test_path_is_dir downstream/.git/modules/submodule/objects/pack
+'
+
+test_done

-- 
2.56.0.307.g5fe7bd680c.dirty


^ permalink raw reply related	[flat|nested] 49+ messages in thread

* Re: [PATCH v3 2/7] setup: introduce object storage extension
  2026-10-23 11:49   ` [PATCH v3 2/7] setup: introduce object storage extension Patrick Steinhardt
@ 2026-10-24 14:50     ` SZEDER Gábor
  2026-10-26 10:05       ` Patrick Steinhardt
  2026-10-26 11:22     ` Karthik Nayak
  1 sibling, 1 reply; 49+ messages in thread
From: SZEDER Gábor @ 2026-10-24 14:50 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, Karthik Nayak

On Fri, Oct 23, 2026 at 01:49:11PM +0200, Patrick Steinhardt wrote:
> Introduce a new "extensions.objectStorage" extension that allows the
> user to configure the primary object database source of a repository.
> The value is a URI in the format "<schema>[://<payload>]", mirroring
> the format used by "extensions.refStorage":
> 
>   - The schema identifies the backend that shall be used. For now the
>     only supported schema is "files", which is the existing object
>     database backed by loose objects and packfiles.
> 
>   - The payload can be passed to configure the backend, and its
>     interpretation is backend-specific. For the "files" backend, it
>     allows you to configure a different location for the ".git/objects"
>     directory.

Please justify why you want to make this configurable.


^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v3 2/7] setup: introduce object storage extension
  2026-10-24 14:50     ` SZEDER Gábor
@ 2026-10-26 10:05       ` Patrick Steinhardt
  0 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-26 10:05 UTC (permalink / raw)
  To: SZEDER Gábor; +Cc: git, Karthik Nayak

On Sat, Oct 24, 2026 at 04:50:20PM +0200, SZEDER Gábor wrote:
> On Fri, Oct 23, 2026 at 01:49:11PM +0200, Patrick Steinhardt wrote:
> > Introduce a new "extensions.objectStorage" extension that allows the
> > user to configure the primary object database source of a repository.
> > The value is a URI in the format "<schema>[://<payload>]", mirroring
> > the format used by "extensions.refStorage":
> > 
> >   - The schema identifies the backend that shall be used. For now the
> >     only supported schema is "files", which is the existing object
> >     database backed by loose objects and packfiles.
> > 
> >   - The payload can be passed to configure the backend, and its
> >     interpretation is backend-specific. For the "files" backend, it
> >     allows you to configure a different location for the ".git/objects"
> >     directory.
> 
> Please justify why you want to make this configurable.

It allows us to already start testing pluggable object databases and the
object storage format extension together with a payload. But more
importantly it matches the semantics that we also have with the ref
storage format, where you can also configure the location.

We also have some use case for this at GitLab as a building block for
migrations. But the above two reasons are the more interesting ones for
now.

I'll adapt the commit message.

Patrick

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v3 2/7] setup: introduce object storage extension
  2026-10-23 11:49   ` [PATCH v3 2/7] setup: introduce object storage extension Patrick Steinhardt
  2026-10-24 14:50     ` SZEDER Gábor
@ 2026-10-26 11:22     ` Karthik Nayak
  2026-10-26 11:53       ` Patrick Steinhardt
  1 sibling, 1 reply; 49+ messages in thread
From: Karthik Nayak @ 2026-10-26 11:22 UTC (permalink / raw)
  To: Patrick Steinhardt, git

[-- Attachment #1: Type: text/plain, Size: 2009 bytes --]

Patrick Steinhardt <ps@pks.im> writes:

[snip]

> diff --git a/odb.c b/odb.c
> index 98c5c862f5..0649622bc6 100644
> --- a/odb.c
> +++ b/odb.c
> @@ -27,6 +27,42 @@
>  #include "trace2.h"
>  #include "write-or-die.h"
>
> +static const char * const object_storage_name_by_format[] = {
> +	[OBJECT_STORAGE_FORMAT_UNKNOWN] = "unknown",
> +	[OBJECT_STORAGE_FORMAT_FILES] = "files",
> +};
> +

Super Nit: I would have dropped from this, as that is the default, this
makes it seem like it is one of the backends. Feel free to ignore
though.

> +enum object_storage_format object_storage_format_by_name(const char *name)
> +{
> +	for (size_t i = 0; i < ARRAY_SIZE(object_storage_name_by_format); i++)
> +		if (!strcmp(object_storage_name_by_format[i], name))
> +			return i;
> +	return OBJECT_STORAGE_FORMAT_UNKNOWN;
> +}
> +
> +enum object_storage_format object_storage_format_by_uri(const char *uri, char **payload)
> +{
> +	enum object_storage_format format;
> +	const char *schema_end;
> +	char *name;
> +
> +	schema_end = strstr(uri, "://");
> +	if (!schema_end) {
> +		name = xstrdup(uri);
> +		if (payload)
> +			*payload = NULL;
> +	} else {
> +		name = xstrndup(uri, schema_end - uri);
> +		if (payload)
> +			*payload = *(schema_end + 3) ? xstrdup(schema_end + 3) : NULL;
> +	}
> +
> +	format = object_storage_format_by_name(name);
> +
> +	free(name);

We need to allocate since we only want to send the format and not the
payload, makes sense.

> +	return format;
> +}
> +
>
>  int odb_mkstemp(struct object_database *odb,
>  		struct strbuf *temp_filename, const char *pattern)
>  {

[snip]

> diff --git a/repository.h b/repository.h
> index 2a348012e8..8c4437762e 100644
> --- a/repository.h
> +++ b/repository.h
> @@ -694,6 +708,7 @@ static enum extension_result handle_extension(const char *var,
>  		data->submodule_path_cfg = git_config_bool(var, value);
>  		return EXTENSION_OK;
>  	}
> +

Nit: stray newline addition

>  	return EXTENSION_UNKNOWN;
>  }
>

[snip]

The patch looks to be in order!

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v3 3/7] setup: allow overriding object storage format via environment
  2026-10-23 11:49   ` [PATCH v3 3/7] setup: allow overriding object storage format via environment Patrick Steinhardt
@ 2026-10-26 11:25     ` Karthik Nayak
  2026-10-26 11:53       ` Patrick Steinhardt
  0 siblings, 1 reply; 49+ messages in thread
From: Karthik Nayak @ 2026-10-26 11:25 UTC (permalink / raw)
  To: Patrick Steinhardt, git

[-- Attachment #1: Type: text/plain, Size: 1587 bytes --]

Patrick Steinhardt <ps@pks.im> writes:

> The ref storage format configured via "extensions.refStorage" can be
> overridden with the GIT_REF_STORAGE_FORMAT environment variable.
> Introduce the GIT_OBJECT_STORAGE_FORMAT environment variable that
> overrides "extensions.objectStorage" in the same way.

[snip]

> diff --git a/setup.c b/setup.c
> index 249bd67876..70aa90a9cb 100644
> --- a/setup.c
> +++ b/setup.c
> @@ -2097,6 +2098,21 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
>  					die(_("unknown ref storage format: '%s'"), ref_backend_uri);
>  			}
>
> +			/*
> +			 * Likewise, the GIT_OBJECT_STORAGE_FORMAT environment

Really nitpicking today: I would drop the 'Likewise' here as this binds
this section of the code to the previous, which may not stay the same
over time.

> +			 * variable should override the repository config for
> +			 * 'extensions.objectStorage'.
> +			 */
> +			object_storage_uri = getenv(GIT_OBJECT_STORAGE_FORMAT_ENVIRONMENT);
> +			if (object_storage_uri) {
> +				FREE_AND_NULL(discovery.format.object_storage_payload);
> +				discovery.format.object_storage_format =
> +					object_storage_format_by_uri(object_storage_uri,
> +								     &discovery.format.object_storage_payload);
> +				if (discovery.format.object_storage_format == OBJECT_STORAGE_FORMAT_UNKNOWN)
> +					die(_("unknown object storage format: '%s'"), object_storage_uri);
> +			}
> +
>  			if (apply_repository_format(repo, &discovery.format,
>  						    APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)
>  				die("%s", err.buf);

[snip]

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v3 0/7] odb: introduce object storage extension
  2026-10-23 11:49 ` [PATCH v3 " Patrick Steinhardt
                     ` (6 preceding siblings ...)
  2026-10-23 11:49   ` [PATCH v3 7/7] builtin/submodule: propagate "--object-storage-format=" Patrick Steinhardt
@ 2026-10-26 11:35   ` Karthik Nayak
  2026-10-26 11:53     ` Patrick Steinhardt
  7 siblings, 1 reply; 49+ messages in thread
From: Karthik Nayak @ 2026-10-26 11:35 UTC (permalink / raw)
  To: Patrick Steinhardt, git

[-- Attachment #1: Type: text/plain, Size: 1557 bytes --]

Patrick Steinhardt <ps@pks.im> writes:

> Hi,
>
> this patch series introduces the object storage extension. The extension
> is of course similar in spirit and design to the ref storage extension:
> it can be configured via "extensions.objectStorage", has the same syntax
> with "<schema>[://<payload>]", and we have the same mechanisms to
> configure it ("--object-storage-format=", GIT_OBJECT_STORAGE_FORMAT,
> GIT_DEFAULT_OBJECT_STORAGE_FORMAT, "init.defaultObjetcStorageFormat").
>
> For now, we only support the "files" object storage format, so not a lot
> of stuff really changes. That being said, its optional payload allows
> you to configure a different location for the ".git/objects" directory.
> So if you specify "extensions.objectStorage=files://objs", objects would
> be stored in ".git/objs" instead.
>
> The series is built on top of c51408889c (The 6th batch, 2026-10-16)
> with the following dependencies merged into it:
>
>   - ps/odb-files-alternates at 0ba59c8453 (odb/source: drop
>     `read_alternates` callback, 2026-10-08) merged into it.
>
>   - ps/setup-enforce-repo-passed-to-create-repository-has-no-state
>     (phew, what a mouthful) at 0ba59c8453 (odb/source: drop
>     `read_alternates` callback, 2026-10-08).
>
> Note that these two series conflict with one another. The conflict can
> easily be resolved by just deleting the whole conflicting sections.
>

I went through the patches since the range-diff was on the larger side.
Apart from some nits, the series looks good. I think we can ignore my
nits too :)

[snip]

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v3 0/7] odb: introduce object storage extension
  2026-10-26 11:35   ` [PATCH v3 0/7] odb: introduce object storage extension Karthik Nayak
@ 2026-10-26 11:53     ` Patrick Steinhardt
  0 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-26 11:53 UTC (permalink / raw)
  To: Karthik Nayak; +Cc: git

On Mon, Oct 26, 2026 at 04:35:27AM -0700, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > Hi,
> >
> > this patch series introduces the object storage extension. The extension
> > is of course similar in spirit and design to the ref storage extension:
> > it can be configured via "extensions.objectStorage", has the same syntax
> > with "<schema>[://<payload>]", and we have the same mechanisms to
> > configure it ("--object-storage-format=", GIT_OBJECT_STORAGE_FORMAT,
> > GIT_DEFAULT_OBJECT_STORAGE_FORMAT, "init.defaultObjetcStorageFormat").
> >
> > For now, we only support the "files" object storage format, so not a lot
> > of stuff really changes. That being said, its optional payload allows
> > you to configure a different location for the ".git/objects" directory.
> > So if you specify "extensions.objectStorage=files://objs", objects would
> > be stored in ".git/objs" instead.
> >
> > The series is built on top of c51408889c (The 6th batch, 2026-10-16)
> > with the following dependencies merged into it:
> >
> >   - ps/odb-files-alternates at 0ba59c8453 (odb/source: drop
> >     `read_alternates` callback, 2026-10-08) merged into it.
> >
> >   - ps/setup-enforce-repo-passed-to-create-repository-has-no-state
> >     (phew, what a mouthful) at 0ba59c8453 (odb/source: drop
> >     `read_alternates` callback, 2026-10-08).
> >
> > Note that these two series conflict with one another. The conflict can
> > easily be resolved by just deleting the whole conflicting sections.
> >
> 
> I went through the patches since the range-diff was on the larger side.
> Apart from some nits, the series looks good. I think we can ignore my
> nits too :)

Thanks! I'll wait a bit and send out a new version tomorrow morning.

Patrick

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v3 2/7] setup: introduce object storage extension
  2026-10-26 11:22     ` Karthik Nayak
@ 2026-10-26 11:53       ` Patrick Steinhardt
  0 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-26 11:53 UTC (permalink / raw)
  To: Karthik Nayak; +Cc: git

On Mon, Oct 26, 2026 at 04:22:38AM -0700, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/odb.c b/odb.c
> > index 98c5c862f5..0649622bc6 100644
> > --- a/odb.c
> > +++ b/odb.c
> > @@ -27,6 +27,42 @@
> >  #include "trace2.h"
> >  #include "write-or-die.h"
> >
> > +static const char * const object_storage_name_by_format[] = {
> > +	[OBJECT_STORAGE_FORMAT_UNKNOWN] = "unknown",
> > +	[OBJECT_STORAGE_FORMAT_FILES] = "files",
> > +};
> > +
> 
> Super Nit: I would have dropped from this, as that is the default, this
> makes it seem like it is one of the backends. Feel free to ignore
> though.

Hm. I need the `OBJECT_STORAGE_FORMAT_UNKNOWN` enum value anyway, so if
I were to drop that backend above I'd have to add another special case
to handle the case where its value is `NULL`. So I'd prefer to keep this
as-is.

> > diff --git a/repository.h b/repository.h
> > index 2a348012e8..8c4437762e 100644
> > --- a/repository.h
> > +++ b/repository.h
> > @@ -694,6 +708,7 @@ static enum extension_result handle_extension(const char *var,
> >  		data->submodule_path_cfg = git_config_bool(var, value);
> >  		return EXTENSION_OK;
> >  	}
> > +
> 
> Nit: stray newline addition

I was confused at first because you snipped to the wrong diff header --
this is in "setup.c", not in "repository.h". Fixed now.

Patrick

^ permalink raw reply	[flat|nested] 49+ messages in thread

* Re: [PATCH v3 3/7] setup: allow overriding object storage format via environment
  2026-10-26 11:25     ` Karthik Nayak
@ 2026-10-26 11:53       ` Patrick Steinhardt
  0 siblings, 0 replies; 49+ messages in thread
From: Patrick Steinhardt @ 2026-10-26 11:53 UTC (permalink / raw)
  To: Karthik Nayak; +Cc: git

On Mon, Oct 26, 2026 at 04:25:11AM -0700, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/setup.c b/setup.c
> > index 249bd67876..70aa90a9cb 100644
> > --- a/setup.c
> > +++ b/setup.c
> > @@ -2097,6 +2098,21 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
> >  					die(_("unknown ref storage format: '%s'"), ref_backend_uri);
> >  			}
> >
> > +			/*
> > +			 * Likewise, the GIT_OBJECT_STORAGE_FORMAT environment
> 
> Really nitpicking today: I would drop the 'Likewise' here as this binds
> this section of the code to the previous, which may not stay the same
> over time.

Fair, can do.

Patrick

^ permalink raw reply	[flat|nested] 49+ messages in thread

end of thread, other threads:[~2026-10-26 11:53 UTC | newest]

Thread overview: 49+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-19  8:55 [PATCH 0/7] odb: introduce object storage extension Patrick Steinhardt
2026-10-19  8:55 ` [PATCH 1/7] loose: drop unused `repo_write_loose_object_map()` Patrick Steinhardt
2026-10-19 20:21   ` Karthik Nayak
2026-10-20  8:49     ` Patrick Steinhardt
2026-10-20 18:30       ` Karthik Nayak
2026-10-19  8:55 ` [PATCH 2/7] setup: introduce object storage extension Patrick Steinhardt
2026-10-19 21:29   ` Karthik Nayak
2026-10-20  1:27     ` Junio C Hamano
2026-10-20 18:32       ` Karthik Nayak
2026-10-20  8:50     ` Patrick Steinhardt
2026-10-20 19:06       ` Karthik Nayak
2026-10-19  8:55 ` [PATCH 3/7] setup: allow overriding object storage format via environment Patrick Steinhardt
2026-10-19 21:34   ` Karthik Nayak
2026-10-20  8:50     ` Patrick Steinhardt
2026-10-20 19:08       ` Karthik Nayak
2026-10-19  8:55 ` [PATCH 4/7] builtin: expose the object storage format Patrick Steinhardt
2026-10-19 21:40   ` Karthik Nayak
2026-10-20  8:50     ` Patrick Steinhardt
2026-10-20 19:10       ` Karthik Nayak
2026-10-19  8:56 ` [PATCH 5/7] builtin/init: introduce handling of " Patrick Steinhardt
2026-10-19  8:56 ` [PATCH 6/7] builtin/clone: introduce "--object-storage-format=" Patrick Steinhardt
2026-10-19  8:56 ` [PATCH 7/7] builtin/submodule: propagate "--object-storage-format=" Patrick Steinhardt
2026-10-21  5:39 ` [PATCH v2 0/7] odb: introduce object storage extension Patrick Steinhardt
2026-10-21  5:39   ` [PATCH v2 1/7] loose: drop unused `repo_write_loose_object_map()` Patrick Steinhardt
2026-10-21  5:39   ` [PATCH v2 2/7] setup: introduce object storage extension Patrick Steinhardt
2026-10-21  5:39   ` [PATCH v2 3/7] setup: allow overriding object storage format via environment Patrick Steinhardt
2026-10-21  5:39   ` [PATCH v2 4/7] builtin: expose the object storage format Patrick Steinhardt
2026-10-21  5:39   ` [PATCH v2 5/7] builtin/init: introduce handling of " Patrick Steinhardt
2026-10-21 14:23     ` Karthik Nayak
2026-10-23  5:33       ` Patrick Steinhardt
2026-10-21  5:39   ` [PATCH v2 6/7] builtin/clone: introduce "--object-storage-format=" Patrick Steinhardt
2026-10-21  5:39   ` [PATCH v2 7/7] builtin/submodule: propagate "--object-storage-format=" Patrick Steinhardt
2026-10-21 14:31   ` [PATCH v2 0/7] odb: introduce object storage extension Karthik Nayak
2026-10-23 11:49 ` [PATCH v3 " Patrick Steinhardt
2026-10-23 11:49   ` [PATCH v3 1/7] loose: drop unused `repo_write_loose_object_map()` Patrick Steinhardt
2026-10-23 11:49   ` [PATCH v3 2/7] setup: introduce object storage extension Patrick Steinhardt
2026-10-24 14:50     ` SZEDER Gábor
2026-10-26 10:05       ` Patrick Steinhardt
2026-10-26 11:22     ` Karthik Nayak
2026-10-26 11:53       ` Patrick Steinhardt
2026-10-23 11:49   ` [PATCH v3 3/7] setup: allow overriding object storage format via environment Patrick Steinhardt
2026-10-26 11:25     ` Karthik Nayak
2026-10-26 11:53       ` Patrick Steinhardt
2026-10-23 11:49   ` [PATCH v3 4/7] builtin: expose the object storage format Patrick Steinhardt
2026-10-23 11:49   ` [PATCH v3 5/7] builtin/init: introduce handling of " Patrick Steinhardt
2026-10-23 11:49   ` [PATCH v3 6/7] builtin/clone: introduce "--object-storage-format=" Patrick Steinhardt
2026-10-23 11:49   ` [PATCH v3 7/7] builtin/submodule: propagate "--object-storage-format=" Patrick Steinhardt
2026-10-26 11:35   ` [PATCH v3 0/7] odb: introduce object storage extension Karthik Nayak
2026-10-26 11:53     ` Patrick Steinhardt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox