* [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
` (6 more replies)
0 siblings, 7 replies; 17+ 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] 17+ 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
` (5 subsequent siblings)
6 siblings, 1 reply; 17+ 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] 17+ 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
` (4 subsequent siblings)
6 siblings, 1 reply; 17+ 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] 17+ 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
` (3 subsequent siblings)
6 siblings, 1 reply; 17+ 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] 17+ 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
` (2 subsequent siblings)
6 siblings, 1 reply; 17+ 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] 17+ 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
2026-10-19 8:56 ` [PATCH 7/7] builtin/submodule: propagate "--object-storage-format=" Patrick Steinhardt
6 siblings, 0 replies; 17+ 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] 17+ 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
6 siblings, 0 replies; 17+ 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] 17+ 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
6 siblings, 0 replies; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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
1 sibling, 0 replies; 17+ 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] 17+ 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
0 siblings, 0 replies; 17+ 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] 17+ 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
1 sibling, 0 replies; 17+ 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] 17+ 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
0 siblings, 0 replies; 17+ 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] 17+ 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
0 siblings, 0 replies; 17+ 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] 17+ messages in thread
end of thread, other threads:[~2026-10-20 8:50 UTC | newest]
Thread overview: 17+ 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-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
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-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-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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox