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