{"thread":{"id":"66000","subject":"[PATCH v1] repository: move fetch_if_missing into struct repository","startedAt":"2026-07-15T01:19:09Z","lastAt":"2026-08-16T06:21:01Z","messageCount":26,"participants":["Tian Yuchen","Junio C Hamano","Patrick Steinhardt"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"548191","messageId":"20260715011850.3181131-1-cat@malon.dev","threadId":"66000","inReplyTo":null,"subject":"[PATCH v1] repository: move fetch_if_missing into struct repository","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-15T01:18:50Z","receivedAt":"2026-07-15T01:19:09Z","isPatch":true,"body":"The global variable 'fetch_if_missing' controls whether a missing\nobject check should prompt a lazy fetch from a promisor remote.\nIn order to continue the libification effort, move it into\n'struct repository' and initialize it to 1 by default to keep the\nprevious behavior.\n\nSubsystems that already pass around a repository pointer, are\nupdated to read this flag directly from their respective 'repo'\ninstances. For the rest, we access 'the_repository'.\n\nNote that in builtin/fsck.c and builtin/index-pack.c, when running\nrelated commands with the '-h' parameter, the 'repo' pointer is not\npassed in. To prevent null pointer dereferences, we defer\noperations on the repo in until after parameter parsing is complete.\n\nAdditionally, update the partial clone documentation to reflect\nthat this is now a per-repository flag.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n Documentation/technical/partial-clone.adoc |  2 +-\n builtin/fetch-pack.c                       |  2 +-\n builtin/fsck.c                             |  6 +++---\n builtin/index-pack.c                       |  9 +++++----\n builtin/pack-objects.c                     | 14 +++++++-------\n builtin/prune.c                            |  2 +-\n builtin/rev-list.c                         | 10 +++++-----\n git.c                                      |  2 +-\n midx-write.c                               |  2 +-\n odb.c                                      |  4 +---\n odb.h                                      |  8 --------\n repository.c                               |  1 +\n repository.h                               |  6 ++++++\n revision.c                                 |  2 +-\n setup.c                                    |  2 +-\n 15 files changed, 35 insertions(+), 37 deletions(-)\n\ndiff --git a/Documentation/technical/partial-clone.adoc b/Documentation/technical/partial-clone.adoc\nindex e513e391ea..18718a3840 100644\n--- a/Documentation/technical/partial-clone.adoc\n+++ b/Documentation/technical/partial-clone.adoc\n@@ -159,7 +159,7 @@ and prefetch those objects in bulk.\n - `repack` in GC has been updated to not touch promisor packfiles at all,\n   and to only repack other objects.\n \n-- The global variable \"fetch_if_missing\" is used to control whether an\n+- The per-repository flag \"fetch_if_missing\" is used to control whether an\n   object lookup will attempt to dynamically fetch a missing object or\n   report an error.\n +\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 316badd969..c5edd7b80f 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -67,7 +67,7 @@ int cmd_fetch_pack(int argc,\n \tstruct packet_reader reader;\n \tenum protocol_version version;\n \n-\tfetch_if_missing = 0;\n+\tthe_repository->fetch_if_missing = 0;\n \n \tpacket_trace_identity(\"fetch-pack\");\n \ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 248f8ff5a0..aa31c69486 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -1017,15 +1017,15 @@ int cmd_fsck(int argc,\n \t\t.ref = NULL\n \t};\n \n-\t/* fsck knows how to handle missing promisor objects */\n-\tfetch_if_missing = 0;\n-\n \terrors_found = 0;\n \tdisable_replace_refs();\n \tsave_commit_buffer = 0;\n \n \targc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);\n \n+\t/* fsck knows how to handle missing promisor objects */\n+\trepo->fetch_if_missing = 0;\n+\n \tfsck_options_init(&fsck_walk_options, repo, FSCK_OPTIONS_DEFAULT);\n \tfsck_walk_options.walk = mark_object;\n \ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex 0793dc595c..721d576938 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1881,7 +1881,7 @@ static void repack_local_links(void)\n int cmd_index_pack(int argc,\n \t\t   const char **argv,\n \t\t   const char *prefix,\n-\t\t   struct repository *repo UNUSED)\n+\t\t   struct repository *repo)\n {\n \tint i, fix_thin_pack = 0, verify = 0, stat_only = 0, rev_index;\n \tconst char *curr_index;\n@@ -1898,15 +1898,16 @@ int cmd_index_pack(int argc,\n \tint report_end_of_input = 0;\n \tint hash_algo = 0;\n \n+\tshow_usage_if_asked(argc, argv, index_pack_usage);\n+\n \t/*\n \t * index-pack never needs to fetch missing objects except when\n \t * REF_DELTA bases are missing (which are explicitly handled). It only\n \t * accesses the repo to do hash collision checks and to check which\n \t * REF_DELTA bases need to be fetched.\n \t */\n-\tfetch_if_missing = 0;\n-\n-\tshow_usage_if_asked(argc, argv, index_pack_usage);\n+\tif (repo)\n+\t\trepo->fetch_if_missing = 0;\n \n \tdisable_replace_refs();\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 8a1709a1ab..c6536b1f65 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4059,7 +4059,7 @@ static void add_unreachable_loose_objects(struct rev_info *revs);\n \n static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n {\n-\tint prev_fetch_if_missing = fetch_if_missing;\n+\tint prev_fetch_if_missing = the_repository->fetch_if_missing;\n \tstruct rev_info revs;\n \n \t/*\n@@ -4067,7 +4067,7 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n \t * walk is best-effort though we don't want to perform backfill fetches\n \t * for them.\n \t */\n-\tfetch_if_missing = 0;\n+\tthe_repository->fetch_if_missing = 0;\n \n \trepo_init_revisions(the_repository, &revs, NULL);\n \t/*\n@@ -4115,7 +4115,7 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n \ttrace2_data_intmax(\"pack-objects\", the_repository, \"stdin_packs_hints\",\n \t\t\t   stdin_packs_hints_nr);\n \n-\tfetch_if_missing = prev_fetch_if_missing;\n+\tthe_repository->fetch_if_missing = prev_fetch_if_missing;\n }\n \n static void add_cruft_object_entry(const struct object_id *oid, enum object_type type,\n@@ -4451,14 +4451,14 @@ static int option_parse_missing_action(const struct option *opt UNUSED,\n \n \tif (!strcmp(arg, \"allow-any\")) {\n \t\targ_missing_action = MA_ALLOW_ANY;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\tfn_show_object = show_object__ma_allow_any;\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(arg, \"allow-promisor\")) {\n \t\targ_missing_action = MA_ALLOW_PROMISOR;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\tfn_show_object = show_object__ma_allow_promisor;\n \t\treturn 0;\n \t}\n@@ -5247,7 +5247,7 @@ int cmd_pack_objects(int argc,\n \t\t\t\t  exclude_promisor_objects_best_effort,\n \t\t\t\t  \"--exclude-promisor-objects-best-effort\");\n \tif (exclude_promisor_objects) {\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \n \t\t/* --stdin-packs handles promisor objects separately. */\n \t\tif (!stdin_packs) {\n@@ -5256,7 +5256,7 @@ int cmd_pack_objects(int argc,\n \t\t}\n \t} else if (exclude_promisor_objects_best_effort) {\n \t\tuse_internal_rev_list = 1;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\toption_parse_missing_action(NULL, \"allow-any\", 0);\n \t\t/* revs configured below */\n \t}\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex 55635a891f..a7e4678d11 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -194,7 +194,7 @@ int cmd_prune(int argc,\n \tif (show_progress == -1)\n \t\tshow_progress = isatty(2);\n \tif (exclude_promisor_objects) {\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\trevs.exclude_promisor_objects = 1;\n \t}\n \ndiff --git a/builtin/rev-list.c b/builtin/rev-list.c\nindex 8f63003709..a6a0c5559e 100644\n--- a/builtin/rev-list.c\n+++ b/builtin/rev-list.c\n@@ -509,25 +509,25 @@ static inline int parse_missing_action_value(const char *value)\n \n \tif (!strcmp(value, \"allow-any\")) {\n \t\targ_missing_action = MA_ALLOW_ANY;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"print\")) {\n \t\targ_missing_action = MA_PRINT;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"print-info\")) {\n \t\targ_missing_action = MA_PRINT_INFO;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"allow-promisor\")) {\n \t\targ_missing_action = MA_ALLOW_PROMISOR;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n@@ -745,7 +745,7 @@ int cmd_rev_list(int argc,\n \tfor (i = 1; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (!strcmp(arg, \"--exclude-promisor-objects\")) {\n-\t\t\tfetch_if_missing = 0;\n+\t\t\tthe_repository->fetch_if_missing = 0;\n \t\t\trevs.exclude_promisor_objects = 1;\n \t\t} else if (skip_prefix(arg, \"--missing=\", &arg)) {\n \t\t\tparse_missing_action_value(arg);\ndiff --git a/git.c b/git.c\nindex 36f08891ef..315d2e160e 100644\n--- a/git.c\n+++ b/git.c\n@@ -202,7 +202,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\n \t\t} else if (!strcmp(cmd, \"--no-lazy-fetch\")) {\n-\t\t\tfetch_if_missing = 0;\n+\t\t\tthe_repository->fetch_if_missing = 0;\n \t\t\tsetenv(NO_LAZY_FETCH_ENVIRONMENT, \"1\", 1);\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\ndiff --git a/midx-write.c b/midx-write.c\nindex 19e1cd10b7..e7313c9d2c 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -865,7 +865,7 @@ static void find_commits_for_midx_bitmap(struct commit_stack *commits,\n \t * complain later that we don't have reachability closure (and fail\n \t * appropriately).\n \t */\n-\tfetch_if_missing = 0;\n+\tctx->repo->fetch_if_missing = 0;\n \trevs.exclude_promisor_objects = 1;\n \n \tif (prepare_revision_walk(&revs))\ndiff --git a/odb.c b/odb.c\nindex 965ef68e4e..664256e1a4 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -528,8 +528,6 @@ void disable_obj_read_lock(void)\n \tpthread_mutex_destroy(&obj_read_mutex);\n }\n \n-int fetch_if_missing = 1;\n-\n static int register_all_submodule_sources(struct object_database *odb)\n {\n \tint ret = odb->submodule_source_paths.nr;\n@@ -595,7 +593,7 @@ static int do_oid_object_info_extended(struct object_database *odb,\n \t\t\tcontinue;\n \n \t\t/* Check if it is a missing object */\n-\t\tif (fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n+\t\tif (odb->repo->fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n \t\t    !already_retried &&\n \t\t    !(flags & OBJECT_INFO_SKIP_FETCH_OBJECT)) {\n \t\t\tpromisor_remote_get_direct(odb->repo, real, 1);\ndiff --git a/odb.h b/odb.h\nindex 0030467a52..1dca583fcb 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -14,14 +14,6 @@ struct repository;\n struct strbuf;\n struct strvec;\n \n-/*\n- * Set this to 0 to prevent odb_read_object_info_extended() from fetching missing\n- * blobs. This has a difference only if extensions.partialClone is set.\n- *\n- * Its default value is 1.\n- */\n-extern int fetch_if_missing;\n-\n /*\n  * Compute the exact path an alternate is at and returns it. In case of\n  * error NULL is returned and the human readable error is added to `err`\ndiff --git a/repository.c b/repository.c\nindex 187dd471c4..b959f7a028 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -73,6 +73,7 @@ void initialize_repository(struct repository *repo)\n \tALLOC_ARRAY(repo->index, 1);\n \tindex_state_init(repo->index, repo);\n \trepo->check_deprecated_config = true;\n+\trepo->fetch_if_missing = 1;\n \trepo_config_values_init(&repo->config_values_private_);\n \n \t/*\ndiff --git a/repository.h b/repository.h\nindex 36e2db2633..e8bd6ef0e7 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -169,6 +169,12 @@ struct repository {\n \t/* True if commit-graph has been disabled within this process. */\n \tint commit_graph_disabled;\n \n+\t/*\n+\t * Controls whether the repository should lazily fetch missing\n+\t * objects from promisor remotes. Defaults to 1.\n+\t */\n+\tint fetch_if_missing;\n+\n \t/*\n \t * Lazily-populated cache mapping hook event names to configured hooks.\n \t * NULL until first hook use.\ndiff --git a/revision.c b/revision.c\nindex e91d7e1f11..bb645654c3 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2714,7 +2714,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t\trevs->ignore_missing = 1;\n \t} else if (opt && opt->allow_exclude_promisor_objects &&\n \t\t   !strcmp(arg, \"--exclude-promisor-objects\")) {\n-\t\tif (fetch_if_missing)\n+\t\tif (revs->repo->fetch_if_missing)\n \t\t\tBUG(\"exclude_promisor_objects can only be used when fetch_if_missing is 0\");\n \t\trevs->exclude_promisor_objects = 1;\n \t} else {\ndiff --git a/setup.c b/setup.c\nindex b4652651df..ce2a80ac31 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1064,7 +1064,7 @@ static void setup_git_env_internal(struct repository *repo,\n \t\tset_alternate_shallow_file(repo, shallow_file, 0);\n \n \tif (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n }\n \n static void set_git_dir_1(struct repository *repo, const char *path)\n-- \n2.43.0\n\n"},{"id":"548193","messageId":"xmqq5x2hq6eb.fsf@gitster.g","threadId":"66000","inReplyTo":"20260715011850.3181131-1-cat@malon.dev","subject":"Re: [PATCH v1] repository: move fetch_if_missing into struct repository","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-15T03:27:24Z","receivedAt":"2026-07-15T03:27:27Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> The global variable 'fetch_if_missing' controls whether a missing\n> object check should prompt a lazy fetch from a promisor remote.\n> In order to continue the libification effort, move it into\n> 'struct repository' and initialize it to 1 by default to keep the\n> previous behavior.\n> ...\n> diff --git a/setup.c b/setup.c\n> index b4652651df..ce2a80ac31 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -1064,7 +1064,7 @@ static void setup_git_env_internal(struct repository *repo,\n>  \t\tset_alternate_shallow_file(repo, shallow_file, 0);\n>  \n>  \tif (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))\n> -\t\tfetch_if_missing = 0;\n> +\t\tthe_repository->fetch_if_missing = 0;\n>  }\n\nCould a caller pass a \"repo\" that is not the_repository?  In other\nwords, shouldn't this be\n\n\t\trepo->fetch_if_missing = 0;\n\ninstead?\n"},{"id":"548204","messageId":"9914b6d6-9876-4c81-bd89-0f6eaf070e9d@malon.dev","threadId":"66000","inReplyTo":"xmqq5x2hq6eb.fsf@gitster.g","subject":"Re: [PATCH v1] repository: move fetch_if_missing into struct repository","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-15T04:58:05Z","receivedAt":"2026-07-15T04:58:13Z","isPatch":true,"body":"On 7/15/26 11:27, Junio C Hamano wrote:\n> Tian Yuchen <cat@malon.dev> writes:\n> \n>> The global variable 'fetch_if_missing' controls whether a missing\n>> object check should prompt a lazy fetch from a promisor remote.\n>> In order to continue the libification effort, move it into\n>> 'struct repository' and initialize it to 1 by default to keep the\n>> previous behavior.\n>> ...\n>> diff --git a/setup.c b/setup.c\n>> index b4652651df..ce2a80ac31 100644\n>> --- a/setup.c\n>> +++ b/setup.c\n>> @@ -1064,7 +1064,7 @@ static void setup_git_env_internal(struct repository *repo,\n>>   \t\tset_alternate_shallow_file(repo, shallow_file, 0);\n>>   \n>>   \tif (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))\n>> -\t\tfetch_if_missing = 0;\n>> +\t\tthe_repository->fetch_if_missing = 0;\n>>   }\n> \n> Could a caller pass a \"repo\" that is not the_repository?  In other\n> words, shouldn't this be\n> \n> \t\trepo->fetch_if_missing = 0;\n> \n> instead?\n\nThanks, will change in the next reroll!\n\nRegards, yuchen\n"},{"id":"548224","messageId":"alcqQp0lkwRIIE1t@pks.im","threadId":"66000","inReplyTo":"20260715011850.3181131-1-cat@malon.dev","subject":"Re: [PATCH v1] repository: move fetch_if_missing into struct repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-07-15T06:35:46Z","receivedAt":"2026-07-15T06:35:53Z","isPatch":true,"body":"On Wed, Jul 15, 2026 at 09:18:50AM +0800, Tian Yuchen wrote:\n> The global variable 'fetch_if_missing' controls whether a missing\n> object check should prompt a lazy fetch from a promisor remote.\n> In order to continue the libification effort, move it into\n> 'struct repository' and initialize it to 1 by default to keep the\n> previous behavior.\n\nRight. I was also thinking about moving this into a non-global scope\nmultiple times. I was approaching this a bit differently though: it's\nultimately a property of the object database whether or not we want to\naccept missing objects, so I moved it in there instead.\n\nI don't really think there's a downside with your version, though. Quite\non the contrary: we can really only perform the backfill fetches with a\nwhole repository at hand anyway. So conceptually your version might even\nbe more sensible.\n\n> Subsystems that already pass around a repository pointer, are\n> updated to read this flag directly from their respective 'repo'\n> instances. For the rest, we access 'the_repository'.\n> \n> Note that in builtin/fsck.c and builtin/index-pack.c, when running\n> related commands with the '-h' parameter, the 'repo' pointer is not\n> passed in. To prevent null pointer dereferences, we defer\n> operations on the repo in until after parameter parsing is complete.\n\ns/on the repo in/on the repo/\n\n> diff --git a/builtin/index-pack.c b/builtin/index-pack.c\n> index 0793dc595c..721d576938 100644\n> --- a/builtin/index-pack.c\n> +++ b/builtin/index-pack.c\n> @@ -1898,15 +1898,16 @@ int cmd_index_pack(int argc,\n>  \tint report_end_of_input = 0;\n>  \tint hash_algo = 0;\n>  \n> +\tshow_usage_if_asked(argc, argv, index_pack_usage);\n> +\n>  \t/*\n>  \t * index-pack never needs to fetch missing objects except when\n>  \t * REF_DELTA bases are missing (which are explicitly handled). It only\n>  \t * accesses the repo to do hash collision checks and to check which\n>  \t * REF_DELTA bases need to be fetched.\n>  \t */\n> -\tfetch_if_missing = 0;\n> -\n> -\tshow_usage_if_asked(argc, argv, index_pack_usage);\n> +\tif (repo)\n> +\t\trepo->fetch_if_missing = 0;\n>  \n>  \tdisable_replace_refs();\n>  \n\nOkay. This command can run without a repository, in which case we'll end\nup just indexing the pack. My assumption is that we'll probably end up\nusing `the_repository` if so, as we still use `the_repository` in this\nfile. So could this here cause a change in behaviour?\n\nIf the answer is \"maybe\" I'd propose that we simply continue to use\n`the_repository` here.\n\n> diff --git a/revision.c b/revision.c\n> index e91d7e1f11..bb645654c3 100644\n> --- a/revision.c\n> +++ b/revision.c\n> @@ -2714,7 +2714,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n>  \t\trevs->ignore_missing = 1;\n>  \t} else if (opt && opt->allow_exclude_promisor_objects &&\n>  \t\t   !strcmp(arg, \"--exclude-promisor-objects\")) {\n> -\t\tif (fetch_if_missing)\n> +\t\tif (revs->repo->fetch_if_missing)\n>  \t\t\tBUG(\"exclude_promisor_objects can only be used when fetch_if_missing is 0\");\n>  \t\trevs->exclude_promisor_objects = 1;\n>  \t} else {\n\nThis one here also makes me wonder whether it could cause weird\ninteractions in case a caller passes a repository other than\n`the_repository`. It ideally _shouldn't_, but it's hard to tell because\nwe still use `the_repository` in lots of places here.\n\nThanks!\n\nPatrick\n"},{"id":"548369","messageId":"ceb95555-d2fa-4877-9e46-eccac5db61d7@malon.dev","threadId":"66000","inReplyTo":"alcqQp0lkwRIIE1t@pks.im","subject":"Re: [PATCH v1] repository: move fetch_if_missing into struct repository","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-16T07:06:56Z","receivedAt":"2026-07-16T07:07:06Z","isPatch":true,"body":"On 7/15/26 14:35, Patrick Steinhardt wrote:\n> On Wed, Jul 15, 2026 at 09:18:50AM +0800, Tian Yuchen wrote:\n>> The global variable 'fetch_if_missing' controls whether a missing\n>> object check should prompt a lazy fetch from a promisor remote.\n>> In order to continue the libification effort, move it into\n>> 'struct repository' and initialize it to 1 by default to keep the\n>> previous behavior.\n> \n> Right. I was also thinking about moving this into a non-global scope\n> multiple times. I was approaching this a bit differently though: it's\n> ultimately a property of the object database whether or not we want to\n> accept missing objects, so I moved it in there instead.\n> \n> I don't really think there's a downside with your version, though. Quite\n> on the contrary: we can really only perform the backfill fetches with a\n> whole repository at hand anyway. So conceptually your version might even\n> be more sensible.\n> \n>> Subsystems that already pass around a repository pointer, are\n>> updated to read this flag directly from their respective 'repo'\n>> instances. For the rest, we access 'the_repository'.\n>>\n>> Note that in builtin/fsck.c and builtin/index-pack.c, when running\n>> related commands with the '-h' parameter, the 'repo' pointer is not\n>> passed in. To prevent null pointer dereferences, we defer\n>> operations on the repo in until after parameter parsing is complete.\n> \n> s/on the repo in/on the repo/\n> \n>> diff --git a/builtin/index-pack.c b/builtin/index-pack.c\n>> index 0793dc595c..721d576938 100644\n>> --- a/builtin/index-pack.c\n>> +++ b/builtin/index-pack.c\n>> @@ -1898,15 +1898,16 @@ int cmd_index_pack(int argc,\n>>   \tint report_end_of_input = 0;\n>>   \tint hash_algo = 0;\n>>   \n>> +\tshow_usage_if_asked(argc, argv, index_pack_usage);\n>> +\n>>   \t/*\n>>   \t * index-pack never needs to fetch missing objects except when\n>>   \t * REF_DELTA bases are missing (which are explicitly handled). It only\n>>   \t * accesses the repo to do hash collision checks and to check which\n>>   \t * REF_DELTA bases need to be fetched.\n>>   \t */\n>> -\tfetch_if_missing = 0;\n>> -\n>> -\tshow_usage_if_asked(argc, argv, index_pack_usage);\n>> +\tif (repo)\n>> +\t\trepo->fetch_if_missing = 0;\n>>   \n>>   \tdisable_replace_refs();\n>>   \n> \n> Okay. This command can run without a repository, in which case we'll end\n> up just indexing the pack. My assumption is that we'll probably end up\n> using `the_repository` if so, as we still use `the_repository` in this\n> file. So could this here cause a change in behaviour?\n\nHummm...\n\n> \n> If the answer is \"maybe\" I'd propose that we simply continue to use\n> `the_repository` here.\n> \n>> diff --git a/revision.c b/revision.c\n>> index e91d7e1f11..bb645654c3 100644\n>> --- a/revision.c\n>> +++ b/revision.c\n>> @@ -2714,7 +2714,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n>>   \t\trevs->ignore_missing = 1;\n>>   \t} else if (opt && opt->allow_exclude_promisor_objects &&\n>>   \t\t   !strcmp(arg, \"--exclude-promisor-objects\")) {\n>> -\t\tif (fetch_if_missing)\n>> +\t\tif (revs->repo->fetch_if_missing)\n>>   \t\t\tBUG(\"exclude_promisor_objects can only be used when fetch_if_missing is 0\");\n>>   \t\trevs->exclude_promisor_objects = 1;\n>>   \t} else {\n> \n> This one here also makes me wonder whether it could cause weird\n> interactions in case a caller passes a repository other than\n> `the_repository`. It ideally _shouldn't_, but it's hard to tell because\n> we still use `the_repository` in lots of places here.\n> \n\nThis makes sense to me. Let's use the_repository then.\n\n> Thanks!\n> \n> Patrick\n\nRegards, yuchen\n"},{"id":"548371","messageId":"20260716072954.582235-1-cat@malon.dev","threadId":"66000","inReplyTo":"20260715011850.3181131-1-cat@malon.dev","subject":"[PATCH v2] repository: move fetch_if_missing into struct repository","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-16T07:29:54Z","receivedAt":"2026-07-16T07:30:07Z","isPatch":true,"body":"The global variable 'fetch_if_missing' controls whether a missing\nobject check should prompt a lazy fetch from a promisor remote.\nIn order to continue the libification effort, move it into\n'struct repository' and initialize it to 1 by default to keep the\nprevious behavior.\n\nNote that in builtin/fsck.c and builtin/index-pack.c, when running\nrelated commands with the '-h' parameter, the 'repo' pointer is not\npassed in. To prevent null pointer dereferences, we defer\noperations on the repo until after parameter parsing is complete.\n\nAdditionally, update the partial clone documentation to reflect\nthat this is now a per-repository flag.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n\nChange since V1:\n\n- Following Patrick's advice, use the_repository whenever possible\n  without re-introducing #define USE_THE_REPOSITORY_VARIABLE.\n\n Documentation/technical/partial-clone.adoc |  2 +-\n builtin/fetch-pack.c                       |  2 +-\n builtin/fsck.c                             |  6 +++---\n builtin/index-pack.c                       |  7 ++++---\n builtin/pack-objects.c                     | 14 +++++++-------\n builtin/prune.c                            |  2 +-\n builtin/rev-list.c                         | 10 +++++-----\n git.c                                      |  2 +-\n midx-write.c                               |  2 +-\n odb.c                                      |  4 +---\n odb.h                                      |  8 --------\n repository.c                               |  1 +\n repository.h                               |  6 ++++++\n revision.c                                 |  2 +-\n setup.c                                    |  2 +-\n 15 files changed, 34 insertions(+), 36 deletions(-)\n\ndiff --git a/Documentation/technical/partial-clone.adoc b/Documentation/technical/partial-clone.adoc\nindex e513e391ea..18718a3840 100644\n--- a/Documentation/technical/partial-clone.adoc\n+++ b/Documentation/technical/partial-clone.adoc\n@@ -159,7 +159,7 @@ and prefetch those objects in bulk.\n - `repack` in GC has been updated to not touch promisor packfiles at all,\n   and to only repack other objects.\n \n-- The global variable \"fetch_if_missing\" is used to control whether an\n+- The per-repository flag \"fetch_if_missing\" is used to control whether an\n   object lookup will attempt to dynamically fetch a missing object or\n   report an error.\n +\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 316badd969..c5edd7b80f 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -67,7 +67,7 @@ int cmd_fetch_pack(int argc,\n \tstruct packet_reader reader;\n \tenum protocol_version version;\n \n-\tfetch_if_missing = 0;\n+\tthe_repository->fetch_if_missing = 0;\n \n \tpacket_trace_identity(\"fetch-pack\");\n \ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 248f8ff5a0..aa31c69486 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -1017,15 +1017,15 @@ int cmd_fsck(int argc,\n \t\t.ref = NULL\n \t};\n \n-\t/* fsck knows how to handle missing promisor objects */\n-\tfetch_if_missing = 0;\n-\n \terrors_found = 0;\n \tdisable_replace_refs();\n \tsave_commit_buffer = 0;\n \n \targc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);\n \n+\t/* fsck knows how to handle missing promisor objects */\n+\trepo->fetch_if_missing = 0;\n+\n \tfsck_options_init(&fsck_walk_options, repo, FSCK_OPTIONS_DEFAULT);\n \tfsck_walk_options.walk = mark_object;\n \ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex 0793dc595c..74f9694662 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1898,15 +1898,16 @@ int cmd_index_pack(int argc,\n \tint report_end_of_input = 0;\n \tint hash_algo = 0;\n \n+\tshow_usage_if_asked(argc, argv, index_pack_usage);\n+\n \t/*\n \t * index-pack never needs to fetch missing objects except when\n \t * REF_DELTA bases are missing (which are explicitly handled). It only\n \t * accesses the repo to do hash collision checks and to check which\n \t * REF_DELTA bases need to be fetched.\n \t */\n-\tfetch_if_missing = 0;\n-\n-\tshow_usage_if_asked(argc, argv, index_pack_usage);\n+\tif (repo)\n+\t\tthe_repository->fetch_if_missing = 0;\n \n \tdisable_replace_refs();\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 8a1709a1ab..c6536b1f65 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4059,7 +4059,7 @@ static void add_unreachable_loose_objects(struct rev_info *revs);\n \n static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n {\n-\tint prev_fetch_if_missing = fetch_if_missing;\n+\tint prev_fetch_if_missing = the_repository->fetch_if_missing;\n \tstruct rev_info revs;\n \n \t/*\n@@ -4067,7 +4067,7 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n \t * walk is best-effort though we don't want to perform backfill fetches\n \t * for them.\n \t */\n-\tfetch_if_missing = 0;\n+\tthe_repository->fetch_if_missing = 0;\n \n \trepo_init_revisions(the_repository, &revs, NULL);\n \t/*\n@@ -4115,7 +4115,7 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n \ttrace2_data_intmax(\"pack-objects\", the_repository, \"stdin_packs_hints\",\n \t\t\t   stdin_packs_hints_nr);\n \n-\tfetch_if_missing = prev_fetch_if_missing;\n+\tthe_repository->fetch_if_missing = prev_fetch_if_missing;\n }\n \n static void add_cruft_object_entry(const struct object_id *oid, enum object_type type,\n@@ -4451,14 +4451,14 @@ static int option_parse_missing_action(const struct option *opt UNUSED,\n \n \tif (!strcmp(arg, \"allow-any\")) {\n \t\targ_missing_action = MA_ALLOW_ANY;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\tfn_show_object = show_object__ma_allow_any;\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(arg, \"allow-promisor\")) {\n \t\targ_missing_action = MA_ALLOW_PROMISOR;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\tfn_show_object = show_object__ma_allow_promisor;\n \t\treturn 0;\n \t}\n@@ -5247,7 +5247,7 @@ int cmd_pack_objects(int argc,\n \t\t\t\t  exclude_promisor_objects_best_effort,\n \t\t\t\t  \"--exclude-promisor-objects-best-effort\");\n \tif (exclude_promisor_objects) {\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \n \t\t/* --stdin-packs handles promisor objects separately. */\n \t\tif (!stdin_packs) {\n@@ -5256,7 +5256,7 @@ int cmd_pack_objects(int argc,\n \t\t}\n \t} else if (exclude_promisor_objects_best_effort) {\n \t\tuse_internal_rev_list = 1;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\toption_parse_missing_action(NULL, \"allow-any\", 0);\n \t\t/* revs configured below */\n \t}\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex 55635a891f..a7e4678d11 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -194,7 +194,7 @@ int cmd_prune(int argc,\n \tif (show_progress == -1)\n \t\tshow_progress = isatty(2);\n \tif (exclude_promisor_objects) {\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\trevs.exclude_promisor_objects = 1;\n \t}\n \ndiff --git a/builtin/rev-list.c b/builtin/rev-list.c\nindex 8f63003709..a6a0c5559e 100644\n--- a/builtin/rev-list.c\n+++ b/builtin/rev-list.c\n@@ -509,25 +509,25 @@ static inline int parse_missing_action_value(const char *value)\n \n \tif (!strcmp(value, \"allow-any\")) {\n \t\targ_missing_action = MA_ALLOW_ANY;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"print\")) {\n \t\targ_missing_action = MA_PRINT;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"print-info\")) {\n \t\targ_missing_action = MA_PRINT_INFO;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"allow-promisor\")) {\n \t\targ_missing_action = MA_ALLOW_PROMISOR;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n@@ -745,7 +745,7 @@ int cmd_rev_list(int argc,\n \tfor (i = 1; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (!strcmp(arg, \"--exclude-promisor-objects\")) {\n-\t\t\tfetch_if_missing = 0;\n+\t\t\tthe_repository->fetch_if_missing = 0;\n \t\t\trevs.exclude_promisor_objects = 1;\n \t\t} else if (skip_prefix(arg, \"--missing=\", &arg)) {\n \t\t\tparse_missing_action_value(arg);\ndiff --git a/git.c b/git.c\nindex 36f08891ef..315d2e160e 100644\n--- a/git.c\n+++ b/git.c\n@@ -202,7 +202,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\n \t\t} else if (!strcmp(cmd, \"--no-lazy-fetch\")) {\n-\t\t\tfetch_if_missing = 0;\n+\t\t\tthe_repository->fetch_if_missing = 0;\n \t\t\tsetenv(NO_LAZY_FETCH_ENVIRONMENT, \"1\", 1);\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\ndiff --git a/midx-write.c b/midx-write.c\nindex 19e1cd10b7..e7313c9d2c 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -865,7 +865,7 @@ static void find_commits_for_midx_bitmap(struct commit_stack *commits,\n \t * complain later that we don't have reachability closure (and fail\n \t * appropriately).\n \t */\n-\tfetch_if_missing = 0;\n+\tctx->repo->fetch_if_missing = 0;\n \trevs.exclude_promisor_objects = 1;\n \n \tif (prepare_revision_walk(&revs))\ndiff --git a/odb.c b/odb.c\nindex 965ef68e4e..664256e1a4 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -528,8 +528,6 @@ void disable_obj_read_lock(void)\n \tpthread_mutex_destroy(&obj_read_mutex);\n }\n \n-int fetch_if_missing = 1;\n-\n static int register_all_submodule_sources(struct object_database *odb)\n {\n \tint ret = odb->submodule_source_paths.nr;\n@@ -595,7 +593,7 @@ static int do_oid_object_info_extended(struct object_database *odb,\n \t\t\tcontinue;\n \n \t\t/* Check if it is a missing object */\n-\t\tif (fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n+\t\tif (odb->repo->fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n \t\t    !already_retried &&\n \t\t    !(flags & OBJECT_INFO_SKIP_FETCH_OBJECT)) {\n \t\t\tpromisor_remote_get_direct(odb->repo, real, 1);\ndiff --git a/odb.h b/odb.h\nindex 0030467a52..1dca583fcb 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -14,14 +14,6 @@ struct repository;\n struct strbuf;\n struct strvec;\n \n-/*\n- * Set this to 0 to prevent odb_read_object_info_extended() from fetching missing\n- * blobs. This has a difference only if extensions.partialClone is set.\n- *\n- * Its default value is 1.\n- */\n-extern int fetch_if_missing;\n-\n /*\n  * Compute the exact path an alternate is at and returns it. In case of\n  * error NULL is returned and the human readable error is added to `err`\ndiff --git a/repository.c b/repository.c\nindex 187dd471c4..b959f7a028 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -73,6 +73,7 @@ void initialize_repository(struct repository *repo)\n \tALLOC_ARRAY(repo->index, 1);\n \tindex_state_init(repo->index, repo);\n \trepo->check_deprecated_config = true;\n+\trepo->fetch_if_missing = 1;\n \trepo_config_values_init(&repo->config_values_private_);\n \n \t/*\ndiff --git a/repository.h b/repository.h\nindex 36e2db2633..e8bd6ef0e7 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -169,6 +169,12 @@ struct repository {\n \t/* True if commit-graph has been disabled within this process. */\n \tint commit_graph_disabled;\n \n+\t/*\n+\t * Controls whether the repository should lazily fetch missing\n+\t * objects from promisor remotes. Defaults to 1.\n+\t */\n+\tint fetch_if_missing;\n+\n \t/*\n \t * Lazily-populated cache mapping hook event names to configured hooks.\n \t * NULL until first hook use.\ndiff --git a/revision.c b/revision.c\nindex e91d7e1f11..5f70aa81e6 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2714,7 +2714,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t\trevs->ignore_missing = 1;\n \t} else if (opt && opt->allow_exclude_promisor_objects &&\n \t\t   !strcmp(arg, \"--exclude-promisor-objects\")) {\n-\t\tif (fetch_if_missing)\n+\t\tif (the_repository->fetch_if_missing)\n \t\t\tBUG(\"exclude_promisor_objects can only be used when fetch_if_missing is 0\");\n \t\trevs->exclude_promisor_objects = 1;\n \t} else {\ndiff --git a/setup.c b/setup.c\nindex b4652651df..ce2a80ac31 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1064,7 +1064,7 @@ static void setup_git_env_internal(struct repository *repo,\n \t\tset_alternate_shallow_file(repo, shallow_file, 0);\n \n \tif (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n }\n \n static void set_git_dir_1(struct repository *repo, const char *path)\n-- \n2.43.0\n\n"},{"id":"548424","messageId":"xmqqh5lzhs22.fsf@gitster.g","threadId":"66000","inReplyTo":"alcqQp0lkwRIIE1t@pks.im","subject":"Re: [PATCH v1] repository: move fetch_if_missing into struct repository","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-16T15:28:53Z","receivedAt":"2026-07-16T15:28:55Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>>  \t/*\n>>  \t * index-pack never needs to fetch missing objects except when\n>>  \t * REF_DELTA bases are missing (which are explicitly handled). It only\n>>  \t * accesses the repo to do hash collision checks and to check which\n>>  \t * REF_DELTA bases need to be fetched.\n>>  \t */\n>> -\tfetch_if_missing = 0;\n>> -\n>> -\tshow_usage_if_asked(argc, argv, index_pack_usage);\n>> +\tif (repo)\n>> +\t\trepo->fetch_if_missing = 0;\n>>  \n>>  \tdisable_replace_refs();\n>>  \n>\n> Okay. This command can run without a repository, in which case we'll end\n> up just indexing the pack. My assumption is that we'll probably end up\n> using `the_repository` if so, as we still use `the_repository` in this\n> file. So could this here cause a change in behaviour?\n\nMeaning that even outside a repository, we could have read the\nsetting from ~/.gitconfig or some other places other than the\nper-repository .git/config file?\n\n"},{"id":"549396","messageId":"e9e36105-7a77-4e5f-8d75-7b160e1d7095@malon.dev","threadId":"66000","inReplyTo":"20260716072954.582235-1-cat@malon.dev","subject":"Re: [PATCH v2] repository: move fetch_if_missing into struct repository","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-08-01T15:53:24Z","receivedAt":"2026-08-01T15:53:40Z","isPatch":true,"body":"On 7/16/26 15:29, Tian Yuchen wrote:\n> The global variable 'fetch_if_missing' controls whether a missing\n> object check should prompt a lazy fetch from a promisor remote.\n> In order to continue the libification effort, move it into\n> 'struct repository' and initialize it to 1 by default to keep the\n> previous behavior.\n> \n> Note that in builtin/fsck.c and builtin/index-pack.c, when running\n> related commands with the '-h' parameter, the 'repo' pointer is not\n> passed in. To prevent null pointer dereferences, we defer\n> operations on the repo until after parameter parsing is complete.\n> \n> Additionally, update the partial clone documentation to reflect\n> that this is now a per-repository flag.\n> \n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n> Signed-off-by: Tian Yuchen <cat@malon.dev>\n> ---\n> \n> Change since V1:\n> \n> - Following Patrick's advice, use the_repository whenever possible\n>    without re-introducing #define USE_THE_REPOSITORY_VARIABLE.\n> \n>   Documentation/technical/partial-clone.adoc |  2 +-\n>   builtin/fetch-pack.c                       |  2 +-\n>   builtin/fsck.c                             |  6 +++---\n>   builtin/index-pack.c                       |  7 ++++---\n>   builtin/pack-objects.c                     | 14 +++++++-------\n>   builtin/prune.c                            |  2 +-\n>   builtin/rev-list.c                         | 10 +++++-----\n>   git.c                                      |  2 +-\n>   midx-write.c                               |  2 +-\n>   odb.c                                      |  4 +---\n>   odb.h                                      |  8 --------\n>   repository.c                               |  1 +\n>   repository.h                               |  6 ++++++\n>   revision.c                                 |  2 +-\n>   setup.c                                    |  2 +-\n>   15 files changed, 34 insertions(+), 36 deletions(-)\n> \n> diff --git a/Documentation/technical/partial-clone.adoc b/Documentation/technical/partial-clone.adoc\n> index e513e391ea..18718a3840 100644\n> --- a/Documentation/technical/partial-clone.adoc\n> +++ b/Documentation/technical/partial-clone.adoc\n> @@ -159,7 +159,7 @@ and prefetch those objects in bulk.\n>   - `repack` in GC has been updated to not touch promisor packfiles at all,\n>     and to only repack other objects.\n>   \n> -- The global variable \"fetch_if_missing\" is used to control whether an\n> +- The per-repository flag \"fetch_if_missing\" is used to control whether an\n>     object lookup will attempt to dynamically fetch a missing object or\n>     report an error.\n>   +\n> diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\n> index 316badd969..c5edd7b80f 100644\n> --- a/builtin/fetch-pack.c\n> +++ b/builtin/fetch-pack.c\n> @@ -67,7 +67,7 @@ int cmd_fetch_pack(int argc,\n>   \tstruct packet_reader reader;\n>   \tenum protocol_version version;\n>   \n> -\tfetch_if_missing = 0;\n> +\tthe_repository->fetch_if_missing = 0;\n>   \n>   \tpacket_trace_identity(\"fetch-pack\");\n>   \n> diff --git a/builtin/fsck.c b/builtin/fsck.c\n> index 248f8ff5a0..aa31c69486 100644\n> --- a/builtin/fsck.c\n> +++ b/builtin/fsck.c\n> @@ -1017,15 +1017,15 @@ int cmd_fsck(int argc,\n>   \t\t.ref = NULL\n>   \t};\n>   \n> -\t/* fsck knows how to handle missing promisor objects */\n> -\tfetch_if_missing = 0;\n> -\n>   \terrors_found = 0;\n>   \tdisable_replace_refs();\n>   \tsave_commit_buffer = 0;\n>   \n>   \targc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);\n>   \n> +\t/* fsck knows how to handle missing promisor objects */\n> +\trepo->fetch_if_missing = 0;\n> +\n>   \tfsck_options_init(&fsck_walk_options, repo, FSCK_OPTIONS_DEFAULT);\n>   \tfsck_walk_options.walk = mark_object;\n>   \n> diff --git a/builtin/index-pack.c b/builtin/index-pack.c\n> index 0793dc595c..74f9694662 100644\n> --- a/builtin/index-pack.c\n> +++ b/builtin/index-pack.c\n> @@ -1898,15 +1898,16 @@ int cmd_index_pack(int argc,\n>   \tint report_end_of_input = 0;\n>   \tint hash_algo = 0;\n>   \n> +\tshow_usage_if_asked(argc, argv, index_pack_usage);\n> +\n>   \t/*\n>   \t * index-pack never needs to fetch missing objects except when\n>   \t * REF_DELTA bases are missing (which are explicitly handled). It only\n>   \t * accesses the repo to do hash collision checks and to check which\n>   \t * REF_DELTA bases need to be fetched.\n>   \t */\n> -\tfetch_if_missing = 0;\n> -\n> -\tshow_usage_if_asked(argc, argv, index_pack_usage);\n> +\tif (repo)\n> +\t\tthe_repository->fetch_if_missing = 0;\n>   \n>   \tdisable_replace_refs();\n>   \n> diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\n> index 8a1709a1ab..c6536b1f65 100644\n> --- a/builtin/pack-objects.c\n> +++ b/builtin/pack-objects.c\n> @@ -4059,7 +4059,7 @@ static void add_unreachable_loose_objects(struct rev_info *revs);\n>   \n>   static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n>   {\n> -\tint prev_fetch_if_missing = fetch_if_missing;\n> +\tint prev_fetch_if_missing = the_repository->fetch_if_missing;\n>   \tstruct rev_info revs;\n>   \n>   \t/*\n> @@ -4067,7 +4067,7 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n>   \t * walk is best-effort though we don't want to perform backfill fetches\n>   \t * for them.\n>   \t */\n> -\tfetch_if_missing = 0;\n> +\tthe_repository->fetch_if_missing = 0;\n>   \n>   \trepo_init_revisions(the_repository, &revs, NULL);\n>   \t/*\n> @@ -4115,7 +4115,7 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n>   \ttrace2_data_intmax(\"pack-objects\", the_repository, \"stdin_packs_hints\",\n>   \t\t\t   stdin_packs_hints_nr);\n>   \n> -\tfetch_if_missing = prev_fetch_if_missing;\n> +\tthe_repository->fetch_if_missing = prev_fetch_if_missing;\n>   }\n>   \n>   static void add_cruft_object_entry(const struct object_id *oid, enum object_type type,\n> @@ -4451,14 +4451,14 @@ static int option_parse_missing_action(const struct option *opt UNUSED,\n>   \n>   \tif (!strcmp(arg, \"allow-any\")) {\n>   \t\targ_missing_action = MA_ALLOW_ANY;\n> -\t\tfetch_if_missing = 0;\n> +\t\tthe_repository->fetch_if_missing = 0;\n>   \t\tfn_show_object = show_object__ma_allow_any;\n>   \t\treturn 0;\n>   \t}\n>   \n>   \tif (!strcmp(arg, \"allow-promisor\")) {\n>   \t\targ_missing_action = MA_ALLOW_PROMISOR;\n> -\t\tfetch_if_missing = 0;\n> +\t\tthe_repository->fetch_if_missing = 0;\n>   \t\tfn_show_object = show_object__ma_allow_promisor;\n>   \t\treturn 0;\n>   \t}\n> @@ -5247,7 +5247,7 @@ int cmd_pack_objects(int argc,\n>   \t\t\t\t  exclude_promisor_objects_best_effort,\n>   \t\t\t\t  \"--exclude-promisor-objects-best-effort\");\n>   \tif (exclude_promisor_objects) {\n> -\t\tfetch_if_missing = 0;\n> +\t\tthe_repository->fetch_if_missing = 0;\n>   \n>   \t\t/* --stdin-packs handles promisor objects separately. */\n>   \t\tif (!stdin_packs) {\n> @@ -5256,7 +5256,7 @@ int cmd_pack_objects(int argc,\n>   \t\t}\n>   \t} else if (exclude_promisor_objects_best_effort) {\n>   \t\tuse_internal_rev_list = 1;\n> -\t\tfetch_if_missing = 0;\n> +\t\tthe_repository->fetch_if_missing = 0;\n>   \t\toption_parse_missing_action(NULL, \"allow-any\", 0);\n>   \t\t/* revs configured below */\n>   \t}\n> diff --git a/builtin/prune.c b/builtin/prune.c\n> index 55635a891f..a7e4678d11 100644\n> --- a/builtin/prune.c\n> +++ b/builtin/prune.c\n> @@ -194,7 +194,7 @@ int cmd_prune(int argc,\n>   \tif (show_progress == -1)\n>   \t\tshow_progress = isatty(2);\n>   \tif (exclude_promisor_objects) {\n> -\t\tfetch_if_missing = 0;\n> +\t\trepo->fetch_if_missing = 0;\n>   \t\trevs.exclude_promisor_objects = 1;\n>   \t}\n>   \n> diff --git a/builtin/rev-list.c b/builtin/rev-list.c\n> index 8f63003709..a6a0c5559e 100644\n> --- a/builtin/rev-list.c\n> +++ b/builtin/rev-list.c\n> @@ -509,25 +509,25 @@ static inline int parse_missing_action_value(const char *value)\n>   \n>   \tif (!strcmp(value, \"allow-any\")) {\n>   \t\targ_missing_action = MA_ALLOW_ANY;\n> -\t\tfetch_if_missing = 0;\n> +\t\tthe_repository->fetch_if_missing = 0;\n>   \t\treturn 1;\n>   \t}\n>   \n>   \tif (!strcmp(value, \"print\")) {\n>   \t\targ_missing_action = MA_PRINT;\n> -\t\tfetch_if_missing = 0;\n> +\t\tthe_repository->fetch_if_missing = 0;\n>   \t\treturn 1;\n>   \t}\n>   \n>   \tif (!strcmp(value, \"print-info\")) {\n>   \t\targ_missing_action = MA_PRINT_INFO;\n> -\t\tfetch_if_missing = 0;\n> +\t\tthe_repository->fetch_if_missing = 0;\n>   \t\treturn 1;\n>   \t}\n>   \n>   \tif (!strcmp(value, \"allow-promisor\")) {\n>   \t\targ_missing_action = MA_ALLOW_PROMISOR;\n> -\t\tfetch_if_missing = 0;\n> +\t\tthe_repository->fetch_if_missing = 0;\n>   \t\treturn 1;\n>   \t}\n>   \n> @@ -745,7 +745,7 @@ int cmd_rev_list(int argc,\n>   \tfor (i = 1; i < argc; i++) {\n>   \t\tconst char *arg = argv[i];\n>   \t\tif (!strcmp(arg, \"--exclude-promisor-objects\")) {\n> -\t\t\tfetch_if_missing = 0;\n> +\t\t\tthe_repository->fetch_if_missing = 0;\n>   \t\t\trevs.exclude_promisor_objects = 1;\n>   \t\t} else if (skip_prefix(arg, \"--missing=\", &arg)) {\n>   \t\t\tparse_missing_action_value(arg);\n> diff --git a/git.c b/git.c\n> index 36f08891ef..315d2e160e 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -202,7 +202,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n>   \t\t\tif (envchanged)\n>   \t\t\t\t*envchanged = 1;\n>   \t\t} else if (!strcmp(cmd, \"--no-lazy-fetch\")) {\n> -\t\t\tfetch_if_missing = 0;\n> +\t\t\tthe_repository->fetch_if_missing = 0;\n>   \t\t\tsetenv(NO_LAZY_FETCH_ENVIRONMENT, \"1\", 1);\n>   \t\t\tif (envchanged)\n>   \t\t\t\t*envchanged = 1;\n> diff --git a/midx-write.c b/midx-write.c\n> index 19e1cd10b7..e7313c9d2c 100644\n> --- a/midx-write.c\n> +++ b/midx-write.c\n> @@ -865,7 +865,7 @@ static void find_commits_for_midx_bitmap(struct commit_stack *commits,\n>   \t * complain later that we don't have reachability closure (and fail\n>   \t * appropriately).\n>   \t */\n> -\tfetch_if_missing = 0;\n> +\tctx->repo->fetch_if_missing = 0;\n>   \trevs.exclude_promisor_objects = 1;\n>   \n>   \tif (prepare_revision_walk(&revs))\n> diff --git a/odb.c b/odb.c\n> index 965ef68e4e..664256e1a4 100644\n> --- a/odb.c\n> +++ b/odb.c\n> @@ -528,8 +528,6 @@ void disable_obj_read_lock(void)\n>   \tpthread_mutex_destroy(&obj_read_mutex);\n>   }\n>   \n> -int fetch_if_missing = 1;\n> -\n>   static int register_all_submodule_sources(struct object_database *odb)\n>   {\n>   \tint ret = odb->submodule_source_paths.nr;\n> @@ -595,7 +593,7 @@ static int do_oid_object_info_extended(struct object_database *odb,\n>   \t\t\tcontinue;\n>   \n>   \t\t/* Check if it is a missing object */\n> -\t\tif (fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n> +\t\tif (odb->repo->fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n>   \t\t    !already_retried &&\n>   \t\t    !(flags & OBJECT_INFO_SKIP_FETCH_OBJECT)) {\n>   \t\t\tpromisor_remote_get_direct(odb->repo, real, 1);\n> diff --git a/odb.h b/odb.h\n> index 0030467a52..1dca583fcb 100644\n> --- a/odb.h\n> +++ b/odb.h\n> @@ -14,14 +14,6 @@ struct repository;\n>   struct strbuf;\n>   struct strvec;\n>   \n> -/*\n> - * Set this to 0 to prevent odb_read_object_info_extended() from fetching missing\n> - * blobs. This has a difference only if extensions.partialClone is set.\n> - *\n> - * Its default value is 1.\n> - */\n> -extern int fetch_if_missing;\n> -\n>   /*\n>    * Compute the exact path an alternate is at and returns it. In case of\n>    * error NULL is returned and the human readable error is added to `err`\n> diff --git a/repository.c b/repository.c\n> index 187dd471c4..b959f7a028 100644\n> --- a/repository.c\n> +++ b/repository.c\n> @@ -73,6 +73,7 @@ void initialize_repository(struct repository *repo)\n>   \tALLOC_ARRAY(repo->index, 1);\n>   \tindex_state_init(repo->index, repo);\n>   \trepo->check_deprecated_config = true;\n> +\trepo->fetch_if_missing = 1;\n>   \trepo_config_values_init(&repo->config_values_private_);\n>   \n>   \t/*\n> diff --git a/repository.h b/repository.h\n> index 36e2db2633..e8bd6ef0e7 100644\n> --- a/repository.h\n> +++ b/repository.h\n> @@ -169,6 +169,12 @@ struct repository {\n>   \t/* True if commit-graph has been disabled within this process. */\n>   \tint commit_graph_disabled;\n>   \n> +\t/*\n> +\t * Controls whether the repository should lazily fetch missing\n> +\t * objects from promisor remotes. Defaults to 1.\n> +\t */\n> +\tint fetch_if_missing;\n> +\n>   \t/*\n>   \t * Lazily-populated cache mapping hook event names to configured hooks.\n>   \t * NULL until first hook use.\n> diff --git a/revision.c b/revision.c\n> index e91d7e1f11..5f70aa81e6 100644\n> --- a/revision.c\n> +++ b/revision.c\n> @@ -2714,7 +2714,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n>   \t\trevs->ignore_missing = 1;\n>   \t} else if (opt && opt->allow_exclude_promisor_objects &&\n>   \t\t   !strcmp(arg, \"--exclude-promisor-objects\")) {\n> -\t\tif (fetch_if_missing)\n> +\t\tif (the_repository->fetch_if_missing)\n>   \t\t\tBUG(\"exclude_promisor_objects can only be used when fetch_if_missing is 0\");\n>   \t\trevs->exclude_promisor_objects = 1;\n>   \t} else {\n> diff --git a/setup.c b/setup.c\n> index b4652651df..ce2a80ac31 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -1064,7 +1064,7 @@ static void setup_git_env_internal(struct repository *repo,\n>   \t\tset_alternate_shallow_file(repo, shallow_file, 0);\n>   \n>   \tif (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))\n> -\t\tfetch_if_missing = 0;\n> +\t\tthe_repository->fetch_if_missing = 0;\n>   }\n>   \n>   static void set_git_dir_1(struct repository *repo, const char *path)\n\nWhat are everyone's thoughts on this patch? It seems like it's been a \nwhile since it received responses. ^^\n\nRegards, yuchen\n"},{"id":"549538","messageId":"anGhtfPaGK7v3NNR@pks.im","threadId":"66000","inReplyTo":"20260716072954.582235-1-cat@malon.dev","subject":"Re: [PATCH v2] repository: move fetch_if_missing into struct repository","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-08-04T08:24:21Z","receivedAt":"2026-08-04T08:24:32Z","isPatch":true,"body":"On Thu, Jul 16, 2026 at 03:29:54PM +0800, Tian Yuchen wrote:\n> The global variable 'fetch_if_missing' controls whether a missing\n> object check should prompt a lazy fetch from a promisor remote.\n> In order to continue the libification effort, move it into\n> 'struct repository' and initialize it to 1 by default to keep the\n> previous behavior.\n> \n> Note that in builtin/fsck.c and builtin/index-pack.c, when running\n> related commands with the '-h' parameter, the 'repo' pointer is not\n> passed in. To prevent null pointer dereferences, we defer\n> operations on the repo until after parameter parsing is complete.\n> \n> Additionally, update the partial clone documentation to reflect\n> that this is now a per-repository flag.\n> \n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n> Signed-off-by: Tian Yuchen <cat@malon.dev>\n> ---\n> \n> Change since V1:\n> \n> - Following Patrick's advice, use the_repository whenever possible\n>   without re-introducing #define USE_THE_REPOSITORY_VARIABLE.\n\nIt would be great to include the range-diff compared to the previous\nversion so that it's easier for the reviewer to spot what's changed.\nTools like b4 automate this for you :)\n\n> diff --git a/builtin/index-pack.c b/builtin/index-pack.c\n> index 0793dc595c..74f9694662 100644\n> --- a/builtin/index-pack.c\n> +++ b/builtin/index-pack.c\n> @@ -1898,15 +1898,16 @@ int cmd_index_pack(int argc,\n>  \tint report_end_of_input = 0;\n>  \tint hash_algo = 0;\n>  \n> +\tshow_usage_if_asked(argc, argv, index_pack_usage);\n> +\n>  \t/*\n>  \t * index-pack never needs to fetch missing objects except when\n>  \t * REF_DELTA bases are missing (which are explicitly handled). It only\n>  \t * accesses the repo to do hash collision checks and to check which\n>  \t * REF_DELTA bases need to be fetched.\n>  \t */\n> -\tfetch_if_missing = 0;\n> -\n> -\tshow_usage_if_asked(argc, argv, index_pack_usage);\n> +\tif (repo)\n> +\t\tthe_repository->fetch_if_missing = 0;\n>  \n>  \tdisable_replace_refs();\n>  \n\nThis one looks a bit weird -- we check for `repo`, but then set\n`the_repository->fetch_if_missing`. We can probably just loose the\nconditional completely, and furthermore we don't need to reorder any\ncode here at all anymore.\n\nOther than that this patch looks good to me, thanks!\n\nPatrick\n"},{"id":"549580","messageId":"xmqqwlu5vla1.fsf@gitster.g","threadId":"66000","inReplyTo":"anGhtfPaGK7v3NNR@pks.im","subject":"Re: [PATCH v2] repository: move fetch_if_missing into struct repository","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-04T17:38:14Z","receivedAt":"2026-08-04T17:38:18Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> diff --git a/builtin/index-pack.c b/builtin/index-pack.c\n>> index 0793dc595c..74f9694662 100644\n>> --- a/builtin/index-pack.c\n>> +++ b/builtin/index-pack.c\n>> @@ -1898,15 +1898,16 @@ int cmd_index_pack(int argc,\n>>  \tint report_end_of_input = 0;\n>>  \tint hash_algo = 0;\n>>  \n>> +\tshow_usage_if_asked(argc, argv, index_pack_usage);\n>> +\n>>  \t/*\n>>  \t * index-pack never needs to fetch missing objects except when\n>>  \t * REF_DELTA bases are missing (which are explicitly handled). It only\n>>  \t * accesses the repo to do hash collision checks and to check which\n>>  \t * REF_DELTA bases need to be fetched.\n>>  \t */\n>> -\tfetch_if_missing = 0;\n>> -\n>> -\tshow_usage_if_asked(argc, argv, index_pack_usage);\n>> +\tif (repo)\n>> +\t\tthe_repository->fetch_if_missing = 0;\n>>  \n>>  \tdisable_replace_refs();\n>>  \n>\n> This one looks a bit weird -- we check for `repo`, but then set\n> `the_repository->fetch_if_missing`. We can probably just loose the\n> conditional completely, and furthermore we don't need to reorder any\n> code here at all anymore.\n\nThe 4-line comment is about disabling fetch-if-missing, so the code\nmovement is not even unnecessary, but it is harmful, I think.  If\nthe command can work without repository, incoming \"repo\" might be\nNULL, and unconditionally doing\n\n\trepo->fetch_if_missing = 0;\n\nmay cause a crash.  But that is not an excuse to blindly add\n\n\tif (repo)\n\nin front of such an assignment.\n\nIt gives you a chance to rethink what you are doing.\n\nIf a command can work without a repository, yet it cares about how\nfetch_if_missing bit is set, it hints that it may be a mistake in\nthe first place to try associating fetch_if_missing bit with a\nparticular struct repository instance, as you must be prepared to\nwork with repo==NULL.\n\nThere could be at least three approaches you may have to think about\nat that point.\n\n * Perhaps the command may not have to work outside a repository at\n   all.  If so, then it is a bug for the caller to call this\n   function with repo==NULL.  So we should just say\n\n\trepo->fetch_if_missing = 0;\n\n   without \"if (repo)\" check at all here.  After all, the situation\n   we might want to enable fetch_if_missing is where we have a place\n   to fetch into, so by definition, we _should_ have a repository in\n   such a case.\n\n * Perhaps the command may want to work outside a repository but it\n   may be acceptable to operate in a degraded way.  By definition,\n   when we are outside a repository, we have no object store to\n   fetch objects lazily into, so fetch_if_missing MUST BE off.\n\n   Because Git is primarily about working inside a repository,\n   perhaps it may be acceptable, even when you are outside a\n   repository, to assume that the_repository can be used as a\n   back-up \"fake repository\" object, and fetch_if_missing and its\n   friends that are necessary to have their meaning to be in that\n   fake repository object.  If that the stance we are going to take,\n   this part should probably say:\n\n\t(repo ? repo : the_repository)->fetch_if_missing = 0;\n\n   We need to make sure that everybody who passes the code paths\n   that ever reference fetch_if_missing would pass the_repository\n   down when the command is running outside a repository, though.\n\n * Or perhaps there are some settings that really need to be\n   available whether you are in a repository or not.  I think\n   fetch_if_missing is a borderline case, but more generally, things\n   like user.name should conceptually be available even outside a\n   repository, with in-repository configuration files overriding\n   them.  And it may be a mistake to force such settings to be\n   stored in an instance of \"struct repository\" (or repo_settings\n   that is part of it).  We would need a framework to represent a\n   structure in which a basic setting, which does not belong to any\n   repository (whose members may be the same as those in \"struct\n   repo_settings\", so I think it is OK to use an instance of that\n   struct to represnt this \"basic settings that is global\"), exists\n   globally, and it is overriden by per repository setting, which is\n   in \"struct repo_settings\" embedded in \"struct repository\".\n\nThe earlier choices require fewer changes than the later choices,\nbut the later choices are more concepturely pure, I think.\n\n\n\n\n"},{"id":"549684","messageId":"286f8e83-1b19-4704-8be1-090772791d8d@malon.dev","threadId":"66000","inReplyTo":"anGhtfPaGK7v3NNR@pks.im","subject":"Re: [PATCH v2] repository: move fetch_if_missing into struct repository","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-08-05T12:10:40Z","receivedAt":"2026-08-05T12:10:48Z","isPatch":true,"body":"Hi Patrick,\n\nOn 8/4/26 16:24, Patrick Steinhardt wrote:\n> On Thu, Jul 16, 2026 at 03:29:54PM +0800, Tian Yuchen wrote:\n>> The global variable 'fetch_if_missing' controls whether a missing\n>> object check should prompt a lazy fetch from a promisor remote.\n>> In order to continue the libification effort, move it into\n>> 'struct repository' and initialize it to 1 by default to keep the\n>> previous behavior.\n>>\n>> Note that in builtin/fsck.c and builtin/index-pack.c, when running\n>> related commands with the '-h' parameter, the 'repo' pointer is not\n>> passed in. To prevent null pointer dereferences, we defer\n>> operations on the repo until after parameter parsing is complete.\n>>\n>> Additionally, update the partial clone documentation to reflect\n>> that this is now a per-repository flag.\n>>\n>> Mentored-by: Christian Couder <christian.couder@gmail.com>\n>> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n>> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n>> Signed-off-by: Tian Yuchen <cat@malon.dev>\n>> ---\n>>\n>> Change since V1:\n>>\n>> - Following Patrick's advice, use the_repository whenever possible\n>>    without re-introducing #define USE_THE_REPOSITORY_VARIABLE.\n> \n> It would be great to include the range-diff compared to the previous\n> version so that it's easier for the reviewer to spot what's changed.\n> Tools like b4 automate this for you :)\n> \n\nI see, I will include it in the next reroll!\n\nThanks! yuchen\n\n"},{"id":"549688","messageId":"48c183b4-3752-4f4a-adb7-0819a956cfc1@malon.dev","threadId":"66000","inReplyTo":"xmqqwlu5vla1.fsf@gitster.g","subject":"Re: [PATCH v2] repository: move fetch_if_missing into struct repository","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-08-05T12:34:30Z","receivedAt":"2026-08-05T12:34:44Z","isPatch":true,"body":"On 8/5/26 01:38, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n>>> diff --git a/builtin/index-pack.c b/builtin/index-pack.c\n>>> index 0793dc595c..74f9694662 100644\n>>> --- a/builtin/index-pack.c\n>>> +++ b/builtin/index-pack.c\n>>> @@ -1898,15 +1898,16 @@ int cmd_index_pack(int argc,\n>>>   \tint report_end_of_input = 0;\n>>>   \tint hash_algo = 0;\n>>>   \n>>> +\tshow_usage_if_asked(argc, argv, index_pack_usage);\n>>> +\n>>>   \t/*\n>>>   \t * index-pack never needs to fetch missing objects except when\n>>>   \t * REF_DELTA bases are missing (which are explicitly handled). It only\n>>>   \t * accesses the repo to do hash collision checks and to check which\n>>>   \t * REF_DELTA bases need to be fetched.\n>>>   \t */\n>>> -\tfetch_if_missing = 0;\n>>> -\n>>> -\tshow_usage_if_asked(argc, argv, index_pack_usage);\n>>> +\tif (repo)\n>>> +\t\tthe_repository->fetch_if_missing = 0;\n>>>   \n>>>   \tdisable_replace_refs();\n>>>   \n>>\n>> This one looks a bit weird -- we check for `repo`, but then set\n>> `the_repository->fetch_if_missing`. We can probably just loose the\n>> conditional completely, and furthermore we don't need to reorder any\n>> code here at all anymore.\n> \n> The 4-line comment is about disabling fetch-if-missing, so the code\n> movement is not even unnecessary, but it is harmful, I think.  If\n> the command can work without repository, incoming \"repo\" might be\n> NULL, and unconditionally doing\n> \n> \trepo->fetch_if_missing = 0;\n> \n> may cause a crash.  But that is not an excuse to blindly add\n> \n> \tif (repo)\n> \n> in front of such an assignment.\n\nYes, this is a mistake. :(\n\n> \n> It gives you a chance to rethink what you are doing.\n> \n> If a command can work without a repository, yet it cares about how\n> fetch_if_missing bit is set, it hints that it may be a mistake in\n> the first place to try associating fetch_if_missing bit with a\n> particular struct repository instance, as you must be prepared to\n> work with repo==NULL.\n> \n> There could be at least three approaches you may have to think about\n> at that point.\n> \n>   * Perhaps the command may not have to work outside a repository at\n>     all.  If so, then it is a bug for the caller to call this\n>     function with repo==NULL.  So we should just say\n> \n> \trepo->fetch_if_missing = 0;\n> \n>     without \"if (repo)\" check at all here.  After all, the situation\n>     we might want to enable fetch_if_missing is where we have a place\n>     to fetch into, so by definition, we _should_ have a repository in\n>     such a case.\n> \n\nYep, this approach looks the most reasonable to me. I think I will apply \nthis approach and update the commit message.\n\n>   * Perhaps the command may want to work outside a repository but it\n>     may be acceptable to operate in a degraded way.  By definition,\n>     when we are outside a repository, we have no object store to\n>     fetch objects lazily into, so fetch_if_missing MUST BE off.\n> \n>     Because Git is primarily about working inside a repository,\n>     perhaps it may be acceptable, even when you are outside a\n>     repository, to assume that the_repository can be used as a\n>     back-up \"fake repository\" object, and fetch_if_missing and its\n>     friends that are necessary to have their meaning to be in that\n>     fake repository object.  If that the stance we are going to take,\n>     this part should probably say:\n> \n> \t(repo ? repo : the_repository)->fetch_if_missing = 0;\n> \n>     We need to make sure that everybody who passes the code paths\n>     that ever reference fetch_if_missing would pass the_repository\n>     down when the command is running outside a repository, though.\n> \n>   * Or perhaps there are some settings that really need to be\n>     available whether you are in a repository or not.  I think\n>     fetch_if_missing is a borderline case, but more generally, things\n>     like user.name should conceptually be available even outside a\n>     repository, with in-repository configuration files overriding\n>     them.  And it may be a mistake to force such settings to be\n>     stored in an instance of \"struct repository\" (or repo_settings\n>     that is part of it).  We would need a framework to represent a\n>     structure in which a basic setting, which does not belong to any\n>     repository (whose members may be the same as those in \"struct\n>     repo_settings\", so I think it is OK to use an instance of that\n>     struct to represnt this \"basic settings that is global\"), exists\n>     globally, and it is overriden by per repository setting, which is\n>     in \"struct repo_settings\" embedded in \"struct repository\".\n> \n\nI'm not sure if I'm right, but I feel that 'fetch_if_missing' \nessentially carries two meanings simultaneously: \"Does this repository \nallow lazy fetching?\" and \"Does this object lookup allow network \nbehavior to be triggered?\" In other words, suppose we consider \n'fetch_if_missing' to meet the third case, allowing it to exist in an \nambiguous situation, i.e. it makes sense both within and outside the \nrepository...Can we then consider the existence of this variable itself \nto have some semantic ambiguity? If so, do we really need to invent \nanother mechanism to accommodate this ambiguity? Or should we \ntemporarily apply minimal changes and discuss the ownership issue when \nthe time is right (i.e. subsystems ready for 'repo' rather than \n'the_repository' only)? Perhaps this is what you mean by \"a borderline \ncase\".\n\nFurthermore: when we do libification, is the goal to eliminate global \nstate, or to \"package global state more reasonably\"? The third approach \nseems to be the latter one.\n\n> The earlier choices require fewer changes than the later choices,\n> but the later choices are more concepturely pure, I think.\n> \n\nAll in all, I think your core point is that we shouldn't blindly put all \nglobal variables in a struct repository. I totally agree with that.\n\n> \n\nThanks! yuchen\n"},{"id":"549968","messageId":"20260807094132.806165-1-cat@malon.dev","threadId":"66000","inReplyTo":"20260716072954.582235-1-cat@malon.dev","subject":"[PATCH v3] repository: move fetch_if_missing into struct repository","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-08-07T09:41:32Z","receivedAt":"2026-08-07T09:41:43Z","isPatch":true,"body":"The global variable 'fetch_if_missing' controls whether a missing\nobject check should prompt a lazy fetch from a promisor remote.\nIn order to continue the libification effort, move it into\n'struct repository' and initialize it to 1 by default to keep the\nprevious behavior.\n\nNote that in builtin/fsck.c and builtin/index-pack.c, when running\nrelated commands with the '-h' parameter, the 'repo' pointer is not\npassed in. To prevent null pointer dereferences, we defer\noperations on the repo until after parameter parsing is complete.\n\nAdditionally, update the partial clone documentation to reflect\nthat this is now a per-repository flag.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\nRange-diff since v2:\n\n    @@ builtin/fsck.c: int cmd_fsck(int argc,\n\n\n      ## builtin/index-pack.c ##\n    +@@ builtin/index-pack.c: static void repack_local_links(void)\n    + int cmd_index_pack(int argc,\n    +              const char **argv,\n    +              const char *prefix,\n    +-             struct repository *repo UNUSED)\n    ++             struct repository *repo)\n    + {\n    +   int i, fix_thin_pack = 0, verify = 0, stat_only = 0, rev_index;\n    +   const char *curr_index;\n     @@ builtin/index-pack.c: int cmd_index_pack(int argc,\n        int report_end_of_input = 0;\n        int hash_algo = 0;\n    @@ builtin/index-pack.c: int cmd_index_pack(int argc,\n     -  fetch_if_missing = 0;\n     -\n     -  show_usage_if_asked(argc, argv, index_pack_usage);\n    -+  if (repo)\n    -+          the_repository->fetch_if_missing = 0;\n    ++  (repo ? repo : the_repository)->fetch_if_missing = 0;\n\n        disable_replace_refs();\n\n Documentation/technical/partial-clone.adoc |  2 +-\n builtin/fetch-pack.c                       |  2 +-\n builtin/fsck.c                             |  6 +++---\n builtin/index-pack.c                       |  8 ++++----\n builtin/pack-objects.c                     | 14 +++++++-------\n builtin/prune.c                            |  2 +-\n builtin/rev-list.c                         | 10 +++++-----\n git.c                                      |  2 +-\n midx-write.c                               |  2 +-\n odb.c                                      |  4 +---\n odb.h                                      |  8 --------\n repository.c                               |  1 +\n repository.h                               |  6 ++++++\n revision.c                                 |  2 +-\n setup.c                                    |  2 +-\n 15 files changed, 34 insertions(+), 37 deletions(-)\n\ndiff --git a/Documentation/technical/partial-clone.adoc b/Documentation/technical/partial-clone.adoc\nindex e513e391ea..18718a3840 100644\n--- a/Documentation/technical/partial-clone.adoc\n+++ b/Documentation/technical/partial-clone.adoc\n@@ -159,7 +159,7 @@ and prefetch those objects in bulk.\n - `repack` in GC has been updated to not touch promisor packfiles at all,\n   and to only repack other objects.\n \n-- The global variable \"fetch_if_missing\" is used to control whether an\n+- The per-repository flag \"fetch_if_missing\" is used to control whether an\n   object lookup will attempt to dynamically fetch a missing object or\n   report an error.\n +\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 316badd969..c5edd7b80f 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -67,7 +67,7 @@ int cmd_fetch_pack(int argc,\n \tstruct packet_reader reader;\n \tenum protocol_version version;\n \n-\tfetch_if_missing = 0;\n+\tthe_repository->fetch_if_missing = 0;\n \n \tpacket_trace_identity(\"fetch-pack\");\n \ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 248f8ff5a0..aa31c69486 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -1017,15 +1017,15 @@ int cmd_fsck(int argc,\n \t\t.ref = NULL\n \t};\n \n-\t/* fsck knows how to handle missing promisor objects */\n-\tfetch_if_missing = 0;\n-\n \terrors_found = 0;\n \tdisable_replace_refs();\n \tsave_commit_buffer = 0;\n \n \targc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);\n \n+\t/* fsck knows how to handle missing promisor objects */\n+\trepo->fetch_if_missing = 0;\n+\n \tfsck_options_init(&fsck_walk_options, repo, FSCK_OPTIONS_DEFAULT);\n \tfsck_walk_options.walk = mark_object;\n \ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex 0793dc595c..1be374806d 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1881,7 +1881,7 @@ static void repack_local_links(void)\n int cmd_index_pack(int argc,\n \t\t   const char **argv,\n \t\t   const char *prefix,\n-\t\t   struct repository *repo UNUSED)\n+\t\t   struct repository *repo)\n {\n \tint i, fix_thin_pack = 0, verify = 0, stat_only = 0, rev_index;\n \tconst char *curr_index;\n@@ -1898,15 +1898,15 @@ int cmd_index_pack(int argc,\n \tint report_end_of_input = 0;\n \tint hash_algo = 0;\n \n+\tshow_usage_if_asked(argc, argv, index_pack_usage);\n+\n \t/*\n \t * index-pack never needs to fetch missing objects except when\n \t * REF_DELTA bases are missing (which are explicitly handled). It only\n \t * accesses the repo to do hash collision checks and to check which\n \t * REF_DELTA bases need to be fetched.\n \t */\n-\tfetch_if_missing = 0;\n-\n-\tshow_usage_if_asked(argc, argv, index_pack_usage);\n+\t(repo ? repo : the_repository)->fetch_if_missing = 0;\n \n \tdisable_replace_refs();\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 8a1709a1ab..c6536b1f65 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4059,7 +4059,7 @@ static void add_unreachable_loose_objects(struct rev_info *revs);\n \n static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n {\n-\tint prev_fetch_if_missing = fetch_if_missing;\n+\tint prev_fetch_if_missing = the_repository->fetch_if_missing;\n \tstruct rev_info revs;\n \n \t/*\n@@ -4067,7 +4067,7 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n \t * walk is best-effort though we don't want to perform backfill fetches\n \t * for them.\n \t */\n-\tfetch_if_missing = 0;\n+\tthe_repository->fetch_if_missing = 0;\n \n \trepo_init_revisions(the_repository, &revs, NULL);\n \t/*\n@@ -4115,7 +4115,7 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n \ttrace2_data_intmax(\"pack-objects\", the_repository, \"stdin_packs_hints\",\n \t\t\t   stdin_packs_hints_nr);\n \n-\tfetch_if_missing = prev_fetch_if_missing;\n+\tthe_repository->fetch_if_missing = prev_fetch_if_missing;\n }\n \n static void add_cruft_object_entry(const struct object_id *oid, enum object_type type,\n@@ -4451,14 +4451,14 @@ static int option_parse_missing_action(const struct option *opt UNUSED,\n \n \tif (!strcmp(arg, \"allow-any\")) {\n \t\targ_missing_action = MA_ALLOW_ANY;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\tfn_show_object = show_object__ma_allow_any;\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(arg, \"allow-promisor\")) {\n \t\targ_missing_action = MA_ALLOW_PROMISOR;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\tfn_show_object = show_object__ma_allow_promisor;\n \t\treturn 0;\n \t}\n@@ -5247,7 +5247,7 @@ int cmd_pack_objects(int argc,\n \t\t\t\t  exclude_promisor_objects_best_effort,\n \t\t\t\t  \"--exclude-promisor-objects-best-effort\");\n \tif (exclude_promisor_objects) {\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \n \t\t/* --stdin-packs handles promisor objects separately. */\n \t\tif (!stdin_packs) {\n@@ -5256,7 +5256,7 @@ int cmd_pack_objects(int argc,\n \t\t}\n \t} else if (exclude_promisor_objects_best_effort) {\n \t\tuse_internal_rev_list = 1;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\toption_parse_missing_action(NULL, \"allow-any\", 0);\n \t\t/* revs configured below */\n \t}\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex 55635a891f..a7e4678d11 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -194,7 +194,7 @@ int cmd_prune(int argc,\n \tif (show_progress == -1)\n \t\tshow_progress = isatty(2);\n \tif (exclude_promisor_objects) {\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\trevs.exclude_promisor_objects = 1;\n \t}\n \ndiff --git a/builtin/rev-list.c b/builtin/rev-list.c\nindex 8f63003709..a6a0c5559e 100644\n--- a/builtin/rev-list.c\n+++ b/builtin/rev-list.c\n@@ -509,25 +509,25 @@ static inline int parse_missing_action_value(const char *value)\n \n \tif (!strcmp(value, \"allow-any\")) {\n \t\targ_missing_action = MA_ALLOW_ANY;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"print\")) {\n \t\targ_missing_action = MA_PRINT;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"print-info\")) {\n \t\targ_missing_action = MA_PRINT_INFO;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"allow-promisor\")) {\n \t\targ_missing_action = MA_ALLOW_PROMISOR;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n@@ -745,7 +745,7 @@ int cmd_rev_list(int argc,\n \tfor (i = 1; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (!strcmp(arg, \"--exclude-promisor-objects\")) {\n-\t\t\tfetch_if_missing = 0;\n+\t\t\tthe_repository->fetch_if_missing = 0;\n \t\t\trevs.exclude_promisor_objects = 1;\n \t\t} else if (skip_prefix(arg, \"--missing=\", &arg)) {\n \t\t\tparse_missing_action_value(arg);\ndiff --git a/git.c b/git.c\nindex 36f08891ef..315d2e160e 100644\n--- a/git.c\n+++ b/git.c\n@@ -202,7 +202,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\n \t\t} else if (!strcmp(cmd, \"--no-lazy-fetch\")) {\n-\t\t\tfetch_if_missing = 0;\n+\t\t\tthe_repository->fetch_if_missing = 0;\n \t\t\tsetenv(NO_LAZY_FETCH_ENVIRONMENT, \"1\", 1);\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\ndiff --git a/midx-write.c b/midx-write.c\nindex 19e1cd10b7..e7313c9d2c 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -865,7 +865,7 @@ static void find_commits_for_midx_bitmap(struct commit_stack *commits,\n \t * complain later that we don't have reachability closure (and fail\n \t * appropriately).\n \t */\n-\tfetch_if_missing = 0;\n+\tctx->repo->fetch_if_missing = 0;\n \trevs.exclude_promisor_objects = 1;\n \n \tif (prepare_revision_walk(&revs))\ndiff --git a/odb.c b/odb.c\nindex 965ef68e4e..664256e1a4 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -528,8 +528,6 @@ void disable_obj_read_lock(void)\n \tpthread_mutex_destroy(&obj_read_mutex);\n }\n \n-int fetch_if_missing = 1;\n-\n static int register_all_submodule_sources(struct object_database *odb)\n {\n \tint ret = odb->submodule_source_paths.nr;\n@@ -595,7 +593,7 @@ static int do_oid_object_info_extended(struct object_database *odb,\n \t\t\tcontinue;\n \n \t\t/* Check if it is a missing object */\n-\t\tif (fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n+\t\tif (odb->repo->fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n \t\t    !already_retried &&\n \t\t    !(flags & OBJECT_INFO_SKIP_FETCH_OBJECT)) {\n \t\t\tpromisor_remote_get_direct(odb->repo, real, 1);\ndiff --git a/odb.h b/odb.h\nindex 0030467a52..1dca583fcb 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -14,14 +14,6 @@ struct repository;\n struct strbuf;\n struct strvec;\n \n-/*\n- * Set this to 0 to prevent odb_read_object_info_extended() from fetching missing\n- * blobs. This has a difference only if extensions.partialClone is set.\n- *\n- * Its default value is 1.\n- */\n-extern int fetch_if_missing;\n-\n /*\n  * Compute the exact path an alternate is at and returns it. In case of\n  * error NULL is returned and the human readable error is added to `err`\ndiff --git a/repository.c b/repository.c\nindex 187dd471c4..b959f7a028 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -73,6 +73,7 @@ void initialize_repository(struct repository *repo)\n \tALLOC_ARRAY(repo->index, 1);\n \tindex_state_init(repo->index, repo);\n \trepo->check_deprecated_config = true;\n+\trepo->fetch_if_missing = 1;\n \trepo_config_values_init(&repo->config_values_private_);\n \n \t/*\ndiff --git a/repository.h b/repository.h\nindex 36e2db2633..e8bd6ef0e7 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -169,6 +169,12 @@ struct repository {\n \t/* True if commit-graph has been disabled within this process. */\n \tint commit_graph_disabled;\n \n+\t/*\n+\t * Controls whether the repository should lazily fetch missing\n+\t * objects from promisor remotes. Defaults to 1.\n+\t */\n+\tint fetch_if_missing;\n+\n \t/*\n \t * Lazily-populated cache mapping hook event names to configured hooks.\n \t * NULL until first hook use.\ndiff --git a/revision.c b/revision.c\nindex e91d7e1f11..5f70aa81e6 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2714,7 +2714,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t\trevs->ignore_missing = 1;\n \t} else if (opt && opt->allow_exclude_promisor_objects &&\n \t\t   !strcmp(arg, \"--exclude-promisor-objects\")) {\n-\t\tif (fetch_if_missing)\n+\t\tif (the_repository->fetch_if_missing)\n \t\t\tBUG(\"exclude_promisor_objects can only be used when fetch_if_missing is 0\");\n \t\trevs->exclude_promisor_objects = 1;\n \t} else {\ndiff --git a/setup.c b/setup.c\nindex b4652651df..ce2a80ac31 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1064,7 +1064,7 @@ static void setup_git_env_internal(struct repository *repo,\n \t\tset_alternate_shallow_file(repo, shallow_file, 0);\n \n \tif (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n }\n \n static void set_git_dir_1(struct repository *repo, const char *path)\n-- \n2.43.0\n\n"},{"id":"550036","messageId":"xmqqwlu1eubr.fsf@gitster.g","threadId":"66000","inReplyTo":"20260807094132.806165-1-cat@malon.dev","subject":"Re: [PATCH v3] repository: move fetch_if_missing into struct repository","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-07T17:03:52Z","receivedAt":"2026-08-07T17:03:55Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> The global variable 'fetch_if_missing' controls whether a missing\n> object check should prompt a lazy fetch from a promisor remote.\n> In order to continue the libification effort, move it into\n> 'struct repository' and initialize it to 1 by default to keep the\n> previous behavior.\n>\n> Note that in builtin/fsck.c and builtin/index-pack.c, when running\n> related commands with the '-h' parameter, the 'repo' pointer is not\n> passed in. To prevent null pointer dereferences, we defer\n> operations on the repo until after parameter parsing is complete.\n>\n> Additionally, update the partial clone documentation to reflect\n> that this is now a per-repository flag.\n>\n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n> Signed-off-by: Tian Yuchen <cat@malon.dev>\n> ---\n\nHave you made a trial merge to 'seen' and 'next' to see how well\nthis change plays with other topics in flight?\n\n> diff --git a/setup.c b/setup.c\n> index b4652651df..ce2a80ac31 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -1064,7 +1064,7 @@ static void setup_git_env_internal(struct repository *repo,\n>  \t\tset_alternate_shallow_file(repo, shallow_file, 0);\n>  \n>  \tif (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))\n> -\t\tfetch_if_missing = 0;\n> +\t\tthe_repository->fetch_if_missing = 0;\n>  }\n\nPelease remind me the reason why we use the_repository here, even\nthough a few lines above we are already using the caller-supplied\nrepo.\n\nAlso setup_git_env_internal() no longer exists in 'seen', if this\ntopic wants to play well with cf1687a41c (setup: split up concerns\nof `setup_git_env_internal()`, 2026-07-07), you'd need to coordinate\nthe other topics and their author(s) before relying on the presence\nof what they are removing.\n"},{"id":"550119","messageId":"2310939d-014f-483b-8ad3-241871b770d5@malon.dev","threadId":"66000","inReplyTo":"xmqqwlu1eubr.fsf@gitster.g","subject":"Re: [PATCH v3] repository: move fetch_if_missing into struct repository","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-08-09T15:00:27Z","receivedAt":"2026-08-09T15:00:46Z","isPatch":true,"body":"On 8/8/26 01:03, Junio C Hamano wrote:\n> Tian Yuchen <cat@malon.dev> writes:\n> \n>> The global variable 'fetch_if_missing' controls whether a missing\n>> object check should prompt a lazy fetch from a promisor remote.\n>> In order to continue the libification effort, move it into\n>> 'struct repository' and initialize it to 1 by default to keep the\n>> previous behavior.\n>>\n>> Note that in builtin/fsck.c and builtin/index-pack.c, when running\n>> related commands with the '-h' parameter, the 'repo' pointer is not\n>> passed in. To prevent null pointer dereferences, we defer\n>> operations on the repo until after parameter parsing is complete.\n>>\n>> Additionally, update the partial clone documentation to reflect\n>> that this is now a per-repository flag.\n>>\n>> Mentored-by: Christian Couder <christian.couder@gmail.com>\n>> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n>> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n>> Signed-off-by: Tian Yuchen <cat@malon.dev>\n>> ---\n> \n> Have you made a trial merge to 'seen' and 'next' to see how well\n> this change plays with other topics in flight?\n> \n\n> Also setup_git_env_internal() no longer exists in 'seen', if this\n> topic wants to play well with cf1687a41c (setup: split up concerns\n> of `setup_git_env_internal()`, 2026-07-07), you'd need to coordinate\n> the other topics and their author(s) before relying on the presence\n> of what they are removing.\n\nThanks for reminding. I did a trial merge and the dependency does exist. \nSince that topic is still in flight, I'd prefer to wait for it to settle \nbefore rerolling this patch. What do you think? ;-)\n\n >> diff --git a/setup.c b/setup.c\n >> index b4652651df..ce2a80ac31 100644\n >> --- a/setup.c\n >> +++ b/setup.c\n >> @@ -1064,7 +1064,7 @@ static void setup_git_env_internal(struct \nrepository *repo,\n >>   \t\tset_alternate_shallow_file(repo, shallow_file, 0);\n >>\n >>   \tif (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))\n >> -\t\tfetch_if_missing = 0;\n >> +\t\tthe_repository->fetch_if_missing = 0;\n >>   }\n >\n > Pelease remind me the reason why we use the_repository here, even\n > though a few lines above we are already using the caller-supplied\n > repo.\n >\n\nInterestingly, in Patrick's patch, setup_environment() doesn't have a \n'repo' passed in and is only called once by init_git(). So I think we'll \nhave to use the_repository, though I never thought of this... Anyway, \nI'll pay more attention to it in the next reroll.\n\n\nRegards, yuchen\n\n\n\n\n"},{"id":"550471","messageId":"20260813061125.1089553-1-cat@malon.dev","threadId":"66000","inReplyTo":"20260807094132.806165-1-cat@malon.dev","subject":"[PATCH v4] repository: move fetch_if_missing into struct repository","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-08-13T06:11:25Z","receivedAt":"2026-08-13T06:11:39Z","isPatch":true,"body":"The global variable 'fetch_if_missing' controls whether a missing\nobject check should prompt a lazy fetch from a promisor remote.\nIn order to continue the libification effort, move it into\n'struct repository' and initialize it to 1 by default to keep the\nprevious behavior.\n\nNote that in builtin/fsck.c and builtin/index-pack.c, when running\nrelated commands with the '-h' parameter, the 'repo' pointer is not\npassed in. To prevent null pointer dereferences, we defer\noperations on the repo until after parameter parsing is complete.\n\nAdditionally, update the partial clone documentation to reflect\nthat this is now a per-repository flag.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n\nChanges since v3:\n\n - Use revs->repo in revision.c instead of the_repository.\n\n - Coordinate the other topics. Specifically, for common-init.c, use\n the_repository->fetch_if_missing in setup_environment(), etc. This patch\n currently does not conflict with seen or next.\n\n Documentation/technical/partial-clone.adoc |  2 +-\n builtin/fetch-pack.c                       |  2 +-\n builtin/fsck.c                             |  6 +++---\n builtin/index-pack.c                       |  8 ++++----\n builtin/pack-objects.c                     | 14 +++++++-------\n builtin/prune.c                            |  2 +-\n builtin/rev-list.c                         | 10 +++++-----\n common-init.c                              |  2 +-\n git.c                                      |  2 +-\n midx-write.c                               |  2 +-\n odb.c                                      |  4 +---\n odb.h                                      |  8 --------\n repository.c                               |  1 +\n repository.h                               |  6 ++++++\n revision.c                                 |  2 +-\n 15 files changed, 34 insertions(+), 37 deletions(-)\n\ndiff --git a/Documentation/technical/partial-clone.adoc b/Documentation/technical/partial-clone.adoc\nindex e513e391ea..18718a3840 100644\n--- a/Documentation/technical/partial-clone.adoc\n+++ b/Documentation/technical/partial-clone.adoc\n@@ -159,7 +159,7 @@ and prefetch those objects in bulk.\n - `repack` in GC has been updated to not touch promisor packfiles at all,\n   and to only repack other objects.\n \n-- The global variable \"fetch_if_missing\" is used to control whether an\n+- The per-repository flag \"fetch_if_missing\" is used to control whether an\n   object lookup will attempt to dynamically fetch a missing object or\n   report an error.\n +\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 316badd969..c5edd7b80f 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -67,7 +67,7 @@ int cmd_fetch_pack(int argc,\n \tstruct packet_reader reader;\n \tenum protocol_version version;\n \n-\tfetch_if_missing = 0;\n+\tthe_repository->fetch_if_missing = 0;\n \n \tpacket_trace_identity(\"fetch-pack\");\n \ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex a6c054e45b..8cfc0e8b26 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -1017,15 +1017,15 @@ int cmd_fsck(int argc,\n \t\t.ref = NULL\n \t};\n \n-\t/* fsck knows how to handle missing promisor objects */\n-\tfetch_if_missing = 0;\n-\n \terrors_found = 0;\n \tdisable_replace_refs();\n \tsave_commit_buffer = 0;\n \n \targc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);\n \n+\t/* fsck knows how to handle missing promisor objects */\n+\trepo->fetch_if_missing = 0;\n+\n \tfsck_options_init(&fsck_walk_options, repo, FSCK_OPTIONS_DEFAULT);\n \tfsck_walk_options.walk = mark_object;\n \ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex bc86925ad0..28f8d01e04 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1886,7 +1886,7 @@ static void repack_local_links(void)\n int cmd_index_pack(int argc,\n \t\t   const char **argv,\n \t\t   const char *prefix,\n-\t\t   struct repository *repo UNUSED)\n+\t\t   struct repository *repo)\n {\n \tint i, fix_thin_pack = 0, verify = 0, stat_only = 0, rev_index;\n \tconst char *curr_index;\n@@ -1903,15 +1903,15 @@ int cmd_index_pack(int argc,\n \tint report_end_of_input = 0;\n \tint hash_algo = 0;\n \n+\tshow_usage_if_asked(argc, argv, index_pack_usage);\n+\n \t/*\n \t * index-pack never needs to fetch missing objects except when\n \t * REF_DELTA bases are missing (which are explicitly handled). It only\n \t * accesses the repo to do hash collision checks and to check which\n \t * REF_DELTA bases need to be fetched.\n \t */\n-\tfetch_if_missing = 0;\n-\n-\tshow_usage_if_asked(argc, argv, index_pack_usage);\n+\t(repo ? repo : the_repository)->fetch_if_missing = 0;\n \n \tdisable_replace_refs();\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 1ec5b6f206..9732b7947a 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4089,7 +4089,7 @@ static void add_unreachable_loose_objects(struct rev_info *revs);\n \n static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n {\n-\tint prev_fetch_if_missing = fetch_if_missing;\n+\tint prev_fetch_if_missing = the_repository->fetch_if_missing;\n \tstruct rev_info revs;\n \n \t/*\n@@ -4097,7 +4097,7 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n \t * walk is best-effort though we don't want to perform backfill fetches\n \t * for them.\n \t */\n-\tfetch_if_missing = 0;\n+\tthe_repository->fetch_if_missing = 0;\n \n \trepo_init_revisions(the_repository, &revs, NULL);\n \t/*\n@@ -4145,7 +4145,7 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n \ttrace2_data_intmax(\"pack-objects\", the_repository, \"stdin_packs_hints\",\n \t\t\t   stdin_packs_hints_nr);\n \n-\tfetch_if_missing = prev_fetch_if_missing;\n+\tthe_repository->fetch_if_missing = prev_fetch_if_missing;\n }\n \n static void add_cruft_object_entry(const struct object_id *oid, enum object_type type,\n@@ -4469,14 +4469,14 @@ static int option_parse_missing_action(const struct option *opt UNUSED,\n \n \tif (!strcmp(arg, \"allow-any\")) {\n \t\targ_missing_action = MA_ALLOW_ANY;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\tfn_show_object = show_object__ma_allow_any;\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(arg, \"allow-promisor\")) {\n \t\targ_missing_action = MA_ALLOW_PROMISOR;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\tfn_show_object = show_object__ma_allow_promisor;\n \t\treturn 0;\n \t}\n@@ -5345,7 +5345,7 @@ int cmd_pack_objects(int argc,\n \t\t\t\t  exclude_promisor_objects_best_effort,\n \t\t\t\t  \"--exclude-promisor-objects-best-effort\");\n \tif (exclude_promisor_objects) {\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \n \t\t/* --stdin-packs handles promisor objects separately. */\n \t\tif (!stdin_packs) {\n@@ -5354,7 +5354,7 @@ int cmd_pack_objects(int argc,\n \t\t}\n \t} else if (exclude_promisor_objects_best_effort) {\n \t\tuse_internal_rev_list = 1;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\toption_parse_missing_action(NULL, \"allow-any\", 0);\n \t\t/* revs configured below */\n \t}\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex 55635a891f..a7e4678d11 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -194,7 +194,7 @@ int cmd_prune(int argc,\n \tif (show_progress == -1)\n \t\tshow_progress = isatty(2);\n \tif (exclude_promisor_objects) {\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\trevs.exclude_promisor_objects = 1;\n \t}\n \ndiff --git a/builtin/rev-list.c b/builtin/rev-list.c\nindex 02818b81c6..40e62baa9f 100644\n--- a/builtin/rev-list.c\n+++ b/builtin/rev-list.c\n@@ -509,25 +509,25 @@ static inline int parse_missing_action_value(const char *value)\n \n \tif (!strcmp(value, \"allow-any\")) {\n \t\targ_missing_action = MA_ALLOW_ANY;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"print\")) {\n \t\targ_missing_action = MA_PRINT;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"print-info\")) {\n \t\targ_missing_action = MA_PRINT_INFO;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"allow-promisor\")) {\n \t\targ_missing_action = MA_ALLOW_PROMISOR;\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n@@ -745,7 +745,7 @@ int cmd_rev_list(int argc,\n \tfor (i = 1; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (!strcmp(arg, \"--exclude-promisor-objects\")) {\n-\t\t\tfetch_if_missing = 0;\n+\t\t\tthe_repository->fetch_if_missing = 0;\n \t\t\trevs.exclude_promisor_objects = 1;\n \t\t} else if (skip_prefix(arg, \"--missing=\", &arg)) {\n \t\t\tparse_missing_action_value(arg);\ndiff --git a/common-init.c b/common-init.c\nindex d26c9c1f20..4a3fa4d7be 100644\n--- a/common-init.c\n+++ b/common-init.c\n@@ -47,7 +47,7 @@ static void setup_environment(void)\n \tupdate_ref_namespace(NAMESPACE_REPLACE, git_replace_ref_base);\n \n \tif (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n }\n \n void init_git(const char **argv)\ndiff --git a/git.c b/git.c\nindex e5f1811b6b..f3ad3aad96 100644\n--- a/git.c\n+++ b/git.c\n@@ -202,7 +202,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\n \t\t} else if (!strcmp(cmd, \"--no-lazy-fetch\")) {\n-\t\t\tfetch_if_missing = 0;\n+\t\t\tthe_repository->fetch_if_missing = 0;\n \t\t\tsetenv(NO_LAZY_FETCH_ENVIRONMENT, \"1\", 1);\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\ndiff --git a/midx-write.c b/midx-write.c\nindex 580724d21a..8537102254 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -865,7 +865,7 @@ static void find_commits_for_midx_bitmap(struct commit_stack *commits,\n \t * complain later that we don't have reachability closure (and fail\n \t * appropriately).\n \t */\n-\tfetch_if_missing = 0;\n+\tctx->repo->fetch_if_missing = 0;\n \trevs.exclude_promisor_objects = 1;\n \n \tif (prepare_revision_walk(&revs))\ndiff --git a/odb.c b/odb.c\nindex dabd481f57..110326f063 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -528,8 +528,6 @@ void disable_obj_read_lock(void)\n \tpthread_mutex_destroy(&obj_read_mutex);\n }\n \n-int fetch_if_missing = 1;\n-\n static int register_all_submodule_sources(struct object_database *odb)\n {\n \tint ret = odb->submodule_source_paths.nr;\n@@ -595,7 +593,7 @@ static int do_oid_object_info_extended(struct object_database *odb,\n \t\t\tcontinue;\n \n \t\t/* Check if it is a missing object */\n-\t\tif (fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n+\t\tif (odb->repo->fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n \t\t    !already_retried &&\n \t\t    !(flags & OBJECT_INFO_SKIP_FETCH_OBJECT)) {\n \t\t\tpromisor_remote_get_direct(odb->repo, real, 1);\ndiff --git a/odb.h b/odb.h\nindex cbc2f9ced4..d3a1e378b6 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -15,14 +15,6 @@ struct repository;\n struct strbuf;\n struct strvec;\n \n-/*\n- * Set this to 0 to prevent odb_read_object_info_extended() from fetching missing\n- * blobs. This has a difference only if extensions.partialClone is set.\n- *\n- * Its default value is 1.\n- */\n-extern int fetch_if_missing;\n-\n /*\n  * Compute the exact path an alternate is at and returns it. In case of\n  * error NULL is returned and the human readable error is added to `err`\ndiff --git a/repository.c b/repository.c\nindex 651b0f6933..c2d954cf83 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -74,6 +74,7 @@ void initialize_repository(struct repository *repo)\n \tindex_state_init(repo->index, repo);\n \trepo->check_deprecated_config = true;\n \trepo->bare_cfg = -1;\n+\trepo->fetch_if_missing = 1;\n \trepo_config_values_init(&repo->config_values_private_);\n \n \t/*\ndiff --git a/repository.h b/repository.h\nindex 3b467a2513..11f5c2ed10 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -184,6 +184,12 @@ struct repository {\n \t/* True if commit-graph has been disabled within this process. */\n \tint commit_graph_disabled;\n \n+\t/*\n+\t * Controls whether the repository should lazily fetch missing\n+\t * objects from promisor remotes. Defaults to 1.\n+\t */\n+\tint fetch_if_missing;\n+\n \t/*\n \t * Lazily-populated cache mapping hook event names to configured hooks.\n \t * NULL until first hook use.\ndiff --git a/revision.c b/revision.c\nindex 526bcf3fb5..40cb1cc828 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2732,7 +2732,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t\trevs->ignore_missing = 1;\n \t} else if (opt && opt->allow_exclude_promisor_objects &&\n \t\t   !strcmp(arg, \"--exclude-promisor-objects\")) {\n-\t\tif (fetch_if_missing)\n+\t\tif (revs->repo->fetch_if_missing)\n \t\t\tBUG(\"exclude_promisor_objects can only be used when fetch_if_missing is 0\");\n \t\trevs->exclude_promisor_objects = 1;\n \t} else {\n-- \n2.43.0\n\n"},{"id":"550536","messageId":"xmqqfr0it207.fsf@gitster.g","threadId":"66000","inReplyTo":"20260813061125.1089553-1-cat@malon.dev","subject":"Re: [PATCH v4] repository: move fetch_if_missing into struct repository","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-13T16:32:24Z","receivedAt":"2026-08-13T16:32:26Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> The global variable 'fetch_if_missing' controls whether a missing\n> object check should prompt a lazy fetch from a promisor remote.\n> In order to continue the libification effort, move it into\n> 'struct repository' and initialize it to 1 by default to keep the\n> previous behavior.\n>\n> Note that in builtin/fsck.c and builtin/index-pack.c, when running\n> related commands with the '-h' parameter, the 'repo' pointer is not\n> passed in. To prevent null pointer dereferences, we defer\n> operations on the repo until after parameter parsing is complete.\n>\n> Additionally, update the partial clone documentation to reflect\n> that this is now a per-repository flag.\n>\n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n> Signed-off-by: Tian Yuchen <cat@malon.dev>\n> ---\n>\n> Changes since v3:\n>\n>  - Use revs->repo in revision.c instead of the_repository.\n>\n>  - Coordinate the other topics. Specifically, for common-init.c, use\n>  the_repository->fetch_if_missing in setup_environment(), etc. This patch\n>  currently does not conflict with seen or next.\n>\n>  Documentation/technical/partial-clone.adoc |  2 +-\n>  builtin/fetch-pack.c                       |  2 +-\n>  builtin/fsck.c                             |  6 +++---\n>  builtin/index-pack.c                       |  8 ++++----\n>  builtin/pack-objects.c                     | 14 +++++++-------\n>  builtin/prune.c                            |  2 +-\n>  builtin/rev-list.c                         | 10 +++++-----\n>  common-init.c                              |  2 +-\n>  git.c                                      |  2 +-\n>  midx-write.c                               |  2 +-\n>  odb.c                                      |  4 +---\n>  odb.h                                      |  8 --------\n>  repository.c                               |  1 +\n>  repository.h                               |  6 ++++++\n>  revision.c                                 |  2 +-\n>  15 files changed, 34 insertions(+), 37 deletions(-)\n\nThere still are references to the_repository->fetch_if_missing\nremaining in the codebase with this change.\n\n        $ git grep -l -e 'the_repository->fetch_if_missing'\n        builtin/fetch-pack.c\n        builtin/pack-objects.c\n        builtin/rev-list.c\n        common-init.c\n        git.c\n\nSome of them I suspect should just use the caller supplied 'repo',\npossibly after removing the UNUSED marker.  For example:\n\n        int cmd_fetch_pack(int argc,\n                           const char **argv,\n                           const char *prefix UNUSED,\n                           struct repository *repo UNUSED)\n        {\n                int i, ret;\n        ...\n                enum protocol_version version;\n\n                the_repository->fetch_if_missing = 0;\n\n                packet_trace_identity(\"fetch-pack\");\n\n                memset(&args, 0, sizeof(args));\n                list_objects_filter_init(&args.filter_options);\n                args.uploadpack = \"git-upload-pack\";\n\n                show_usage_if_asked(argc, argv, fetch_pack_usage);\n\nAs { \"fetch-pack\", cmd_fetch_pack } in the git.c:commands[] array is\nmarked as RUN_SETUP, repo will not be NULL unless \"git fetch-pack -h\"\nis requested, and when repo is NULL, show_usage_if_asked() will give\nthe short help text and never return.\n\nSo I think it makes sense to set 'fetch_if_missing' *after* the call\nto show_usage_if_asked() and set it in 'repo', not in 'the_repository'.\n\nOther hits in the above \"git grep\" output looked similar.  The code\npaths in pack-objects.c may need a preliminary clean-up patch (or\ntwo) before moving fetch_if_missing to the repository instance.\nI.e., pass repo through the call graph from cmd_pack_objects() to\nread_stdin_packs(), and then update assignments to fetch_if_missing\nvariable to instead assign to repo->fetch_if_missing in a second\nstep.  There are other code paths that want similar clean-up.\n\nHTH.\n"},{"id":"550590","messageId":"20260814072419.1666358-1-cat@malon.dev","threadId":"66000","inReplyTo":"20260813061125.1089553-1-cat@malon.dev","subject":"[PATCH v5 0/2] repository: move fetch_if_missing into struct repository","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-08-14T07:24:17Z","receivedAt":"2026-08-14T07:24:26Z","isPatch":true,"body":"Hi all,\n\nTo continue the libification effort, this commit series aim to migrate\nthe global 'fetch_if_missing' into struct repository, making it a\nper-repository configuration.\n\nChanges since v4:\n\n - Use repo instead of the_repository in more call sites.\n\nTian Yuchen (2):\n  pack-objects: give fetch_if_missing call sites access to 'repo'\n  repository: move fetch_if_missing into struct repository\n\n Documentation/technical/partial-clone.adoc |  2 +-\n builtin/fetch-pack.c                       |  6 +++---\n builtin/fsck.c                             |  6 +++---\n builtin/index-pack.c                       |  8 +++----\n builtin/pack-objects.c                     | 25 ++++++++++++----------\n builtin/prune.c                            |  2 +-\n builtin/rev-list.c                         | 17 ++++++++-------\n common-init.c                              |  2 +-\n git.c                                      |  2 +-\n midx-write.c                               |  2 +-\n odb.c                                      |  4 +---\n odb.h                                      |  8 -------\n repository.c                               |  1 +\n repository.h                               |  6 ++++++\n revision.c                                 |  2 +-\n 15 files changed, 47 insertions(+), 46 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"550591","messageId":"20260814072419.1666358-2-cat@malon.dev","threadId":"66000","inReplyTo":"20260814072419.1666358-1-cat@malon.dev","subject":"[PATCH v5 1/2] pack-objects: give fetch_if_missing call sites access to 'repo'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-08-14T07:24:18Z","receivedAt":"2026-08-14T07:24:28Z","isPatch":true,"body":"In order to move the global 'fetch_if_missing' variable into 'struct\nrepository' in a follow-up commit, first make sure every call site in\nbuiltin/pack-objects.c has a 'struct repository *'.\n\n'cmd_pack_objects()' already receives a 'repo' parameter. Drop the\nUNUSED mark.\n\n'option_parse_missing_action()' is registered as an OPT_CALLBACK, so\nits signature is fixed and cannot easily gain a 'repo' parameter of\nits own. Let it only record 'arg_missing_action'. Instead, apply the\nside effect right after 'parse_options()' returns in\n'cmd_pack_objects()', where 'repo' is available.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n builtin/pack-objects.c | 15 +++++++++------\n 1 file changed, 9 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 1ec5b6f206..2b14dd2f31 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4087,7 +4087,8 @@ static void stdin_packs_read_input(struct rev_info *revs,\n \n static void add_unreachable_loose_objects(struct rev_info *revs);\n \n-static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n+static void read_stdin_packs(struct repository *repo,\n+\t\t\t     enum stdin_packs_mode mode, int rev_list_unpacked)\n {\n \tint prev_fetch_if_missing = fetch_if_missing;\n \tstruct rev_info revs;\n@@ -4099,7 +4100,7 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n \t */\n \tfetch_if_missing = 0;\n \n-\trepo_init_revisions(the_repository, &revs, NULL);\n+\trepo_init_revisions(repo, &revs, NULL);\n \t/*\n \t * Use a revision walk to fill in the namehash of objects in the include\n \t * packs. To save time, we'll avoid traversing through objects that are\n@@ -4469,14 +4470,12 @@ static int option_parse_missing_action(const struct option *opt UNUSED,\n \n \tif (!strcmp(arg, \"allow-any\")) {\n \t\targ_missing_action = MA_ALLOW_ANY;\n-\t\tfetch_if_missing = 0;\n \t\tfn_show_object = show_object__ma_allow_any;\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(arg, \"allow-promisor\")) {\n \t\targ_missing_action = MA_ALLOW_PROMISOR;\n-\t\tfetch_if_missing = 0;\n \t\tfn_show_object = show_object__ma_allow_promisor;\n \t\treturn 0;\n \t}\n@@ -5118,7 +5117,7 @@ static int parse_stdin_packs_mode(const struct option *opt, const char *arg,\n int cmd_pack_objects(int argc,\n \t\t     const char **argv,\n \t\t     const char *prefix,\n-\t\t     struct repository *repo UNUSED)\n+\t\t     struct repository *repo)\n {\n \tint use_internal_rev_list = 0;\n \tint all_progress_implied = 0;\n@@ -5267,6 +5266,10 @@ int cmd_pack_objects(int argc,\n \targc = parse_options(argc, argv, prefix, pack_objects_options,\n \t\t\t     pack_usage, 0);\n \n+\tif (arg_missing_action == MA_ALLOW_ANY ||\n+\t    arg_missing_action == MA_ALLOW_PROMISOR)\n+\t\tfetch_if_missing = 0;\n+\n \tif (argc) {\n \t\tbase_name = argv[0];\n \t\targc--;\n@@ -5471,7 +5474,7 @@ int cmd_pack_objects(int argc,\n \t\tprogress_state = start_progress(the_repository,\n \t\t\t\t\t\t_(\"Enumerating objects\"), 0);\n \tif (stdin_packs) {\n-\t\tread_stdin_packs(stdin_packs, rev_list_unpacked);\n+\t\tread_stdin_packs(repo, stdin_packs, rev_list_unpacked);\n \t} else if (cruft) {\n \t\tread_cruft_objects();\n \t} else if (!use_internal_rev_list) {\n-- \n2.43.0\n\n"},{"id":"550592","messageId":"20260814072419.1666358-3-cat@malon.dev","threadId":"66000","inReplyTo":"20260814072419.1666358-1-cat@malon.dev","subject":"[PATCH v5 2/2] repository: move fetch_if_missing into struct repository","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-08-14T07:24:19Z","receivedAt":"2026-08-14T07:24:31Z","isPatch":true,"body":"The global variable 'fetch_if_missing' controls whether a missing\nobject check should prompt a lazy fetch from a promisor remote.\nIn order to continue the libification effort, move it into\n'struct repository' and initialize it to 1 by default to keep the\nprevious behavior.\n\nbuiltin/fetch-pack.c, builtin/fsck.c, and builtin/rev-list.c are\nentered via commands marked RUN_SETUP in git.c:commands[]. Their\n'repo' parameter is only NULL when '-h' is given outside of a\nrepository, in which case either show_usage_if_asked() or\nparse_options()'s own '-h' handling exits the process before\nreturning. We can therefore drop their UNUSED markers and assign\nto 'repo' directly.\n\nbuiltin/index-pack.c  is entered via RUN_SETUP_GENTLY, so its\n'repo' pointer can be NULL any time it is run outside of a\nrepository, not only with '-h'. We keep a NULL check there and fall\nback to 'the_repository'.\n\nbuiltin/pack-objects.c's call sites were prepared in the preceding\ncommit to have 'repo' in scope. This commit performs the replacement\nof 'fetch_if_missing' with 'repo->fetch_if_missing' there.\n\nAdditionally, update the partial clone documentation to reflect\nthat this is now a per-repository flag.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n Documentation/technical/partial-clone.adoc |  2 +-\n builtin/fetch-pack.c                       |  6 +++---\n builtin/fsck.c                             |  6 +++---\n builtin/index-pack.c                       |  8 ++++----\n builtin/pack-objects.c                     | 12 ++++++------\n builtin/prune.c                            |  2 +-\n builtin/rev-list.c                         | 17 +++++++++--------\n common-init.c                              |  2 +-\n git.c                                      |  2 +-\n midx-write.c                               |  2 +-\n odb.c                                      |  4 +---\n odb.h                                      |  8 --------\n repository.c                               |  1 +\n repository.h                               |  6 ++++++\n revision.c                                 |  2 +-\n 15 files changed, 39 insertions(+), 41 deletions(-)\n\ndiff --git a/Documentation/technical/partial-clone.adoc b/Documentation/technical/partial-clone.adoc\nindex e513e391ea..18718a3840 100644\n--- a/Documentation/technical/partial-clone.adoc\n+++ b/Documentation/technical/partial-clone.adoc\n@@ -159,7 +159,7 @@ and prefetch those objects in bulk.\n - `repack` in GC has been updated to not touch promisor packfiles at all,\n   and to only repack other objects.\n \n-- The global variable \"fetch_if_missing\" is used to control whether an\n+- The per-repository flag \"fetch_if_missing\" is used to control whether an\n   object lookup will attempt to dynamically fetch a missing object or\n   report an error.\n +\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 316badd969..86754296fa 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -49,7 +49,7 @@ static void add_sought_entry(struct ref ***sought, int *nr, int *alloc,\n int cmd_fetch_pack(int argc,\n \t\t   const char **argv,\n \t\t   const char *prefix UNUSED,\n-\t\t   struct repository *repo UNUSED)\n+\t\t   struct repository *repo)\n {\n \tint i, ret;\n \tstruct ref *fetched_refs = NULL, *remote_refs = NULL;\n@@ -67,8 +67,6 @@ int cmd_fetch_pack(int argc,\n \tstruct packet_reader reader;\n \tenum protocol_version version;\n \n-\tfetch_if_missing = 0;\n-\n \tpacket_trace_identity(\"fetch-pack\");\n \n \tmemset(&args, 0, sizeof(args));\n@@ -77,6 +75,8 @@ int cmd_fetch_pack(int argc,\n \n \tshow_usage_if_asked(argc, argv, fetch_pack_usage);\n \n+\trepo->fetch_if_missing = 0;\n+\n \tfor (i = 1; i < argc && *argv[i] == '-'; i++) {\n \t\tconst char *arg = argv[i];\n \ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex a6c054e45b..8cfc0e8b26 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -1017,15 +1017,15 @@ int cmd_fsck(int argc,\n \t\t.ref = NULL\n \t};\n \n-\t/* fsck knows how to handle missing promisor objects */\n-\tfetch_if_missing = 0;\n-\n \terrors_found = 0;\n \tdisable_replace_refs();\n \tsave_commit_buffer = 0;\n \n \targc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);\n \n+\t/* fsck knows how to handle missing promisor objects */\n+\trepo->fetch_if_missing = 0;\n+\n \tfsck_options_init(&fsck_walk_options, repo, FSCK_OPTIONS_DEFAULT);\n \tfsck_walk_options.walk = mark_object;\n \ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex bc86925ad0..28f8d01e04 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1886,7 +1886,7 @@ static void repack_local_links(void)\n int cmd_index_pack(int argc,\n \t\t   const char **argv,\n \t\t   const char *prefix,\n-\t\t   struct repository *repo UNUSED)\n+\t\t   struct repository *repo)\n {\n \tint i, fix_thin_pack = 0, verify = 0, stat_only = 0, rev_index;\n \tconst char *curr_index;\n@@ -1903,15 +1903,15 @@ int cmd_index_pack(int argc,\n \tint report_end_of_input = 0;\n \tint hash_algo = 0;\n \n+\tshow_usage_if_asked(argc, argv, index_pack_usage);\n+\n \t/*\n \t * index-pack never needs to fetch missing objects except when\n \t * REF_DELTA bases are missing (which are explicitly handled). It only\n \t * accesses the repo to do hash collision checks and to check which\n \t * REF_DELTA bases need to be fetched.\n \t */\n-\tfetch_if_missing = 0;\n-\n-\tshow_usage_if_asked(argc, argv, index_pack_usage);\n+\t(repo ? repo : the_repository)->fetch_if_missing = 0;\n \n \tdisable_replace_refs();\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 2b14dd2f31..7dea8940a0 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4090,7 +4090,7 @@ static void add_unreachable_loose_objects(struct rev_info *revs);\n static void read_stdin_packs(struct repository *repo,\n \t\t\t     enum stdin_packs_mode mode, int rev_list_unpacked)\n {\n-\tint prev_fetch_if_missing = fetch_if_missing;\n+\tint prev_fetch_if_missing = repo->fetch_if_missing;\n \tstruct rev_info revs;\n \n \t/*\n@@ -4098,7 +4098,7 @@ static void read_stdin_packs(struct repository *repo,\n \t * walk is best-effort though we don't want to perform backfill fetches\n \t * for them.\n \t */\n-\tfetch_if_missing = 0;\n+\trepo->fetch_if_missing = 0;\n \n \trepo_init_revisions(repo, &revs, NULL);\n \t/*\n@@ -4146,7 +4146,7 @@ static void read_stdin_packs(struct repository *repo,\n \ttrace2_data_intmax(\"pack-objects\", the_repository, \"stdin_packs_hints\",\n \t\t\t   stdin_packs_hints_nr);\n \n-\tfetch_if_missing = prev_fetch_if_missing;\n+\trepo->fetch_if_missing = prev_fetch_if_missing;\n }\n \n static void add_cruft_object_entry(const struct object_id *oid, enum object_type type,\n@@ -5268,7 +5268,7 @@ int cmd_pack_objects(int argc,\n \n \tif (arg_missing_action == MA_ALLOW_ANY ||\n \t    arg_missing_action == MA_ALLOW_PROMISOR)\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \n \tif (argc) {\n \t\tbase_name = argv[0];\n@@ -5348,7 +5348,7 @@ int cmd_pack_objects(int argc,\n \t\t\t\t  exclude_promisor_objects_best_effort,\n \t\t\t\t  \"--exclude-promisor-objects-best-effort\");\n \tif (exclude_promisor_objects) {\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \n \t\t/* --stdin-packs handles promisor objects separately. */\n \t\tif (!stdin_packs) {\n@@ -5357,7 +5357,7 @@ int cmd_pack_objects(int argc,\n \t\t}\n \t} else if (exclude_promisor_objects_best_effort) {\n \t\tuse_internal_rev_list = 1;\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\toption_parse_missing_action(NULL, \"allow-any\", 0);\n \t\t/* revs configured below */\n \t}\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex 55635a891f..a7e4678d11 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -194,7 +194,7 @@ int cmd_prune(int argc,\n \tif (show_progress == -1)\n \t\tshow_progress = isatty(2);\n \tif (exclude_promisor_objects) {\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\trevs.exclude_promisor_objects = 1;\n \t}\n \ndiff --git a/builtin/rev-list.c b/builtin/rev-list.c\nindex 02818b81c6..6b596231ab 100644\n--- a/builtin/rev-list.c\n+++ b/builtin/rev-list.c\n@@ -500,7 +500,8 @@ static void print_disk_usage(off_t size)\n \tstrbuf_release(&sb);\n }\n \n-static inline int parse_missing_action_value(const char *value)\n+static inline int parse_missing_action_value(struct repository *repo,\n+\t\t\t\t\t     const char *value)\n {\n \tif (!strcmp(value, \"error\")) {\n \t\targ_missing_action = MA_ERROR;\n@@ -509,25 +510,25 @@ static inline int parse_missing_action_value(const char *value)\n \n \tif (!strcmp(value, \"allow-any\")) {\n \t\targ_missing_action = MA_ALLOW_ANY;\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"print\")) {\n \t\targ_missing_action = MA_PRINT;\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"print-info\")) {\n \t\targ_missing_action = MA_PRINT_INFO;\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"allow-promisor\")) {\n \t\targ_missing_action = MA_ALLOW_PROMISOR;\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n@@ -692,7 +693,7 @@ static void prepare_maximal_independent(struct rev_info *revs)\n int cmd_rev_list(int argc,\n \t\t const char **argv,\n \t\t const char *prefix,\n-\t\t struct repository *repo UNUSED)\n+\t\t struct repository *repo)\n {\n \tstruct rev_info revs;\n \tstruct rev_list_info info;\n@@ -745,10 +746,10 @@ int cmd_rev_list(int argc,\n \tfor (i = 1; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (!strcmp(arg, \"--exclude-promisor-objects\")) {\n-\t\t\tfetch_if_missing = 0;\n+\t\t\trepo->fetch_if_missing = 0;\n \t\t\trevs.exclude_promisor_objects = 1;\n \t\t} else if (skip_prefix(arg, \"--missing=\", &arg)) {\n-\t\t\tparse_missing_action_value(arg);\n+\t\t\tparse_missing_action_value(repo, arg);\n \t\t} else if (!strcmp(arg, \"-z\")) {\n \t\t\tline_term = '\\0';\n \t\t\tinfo_term = '\\0';\ndiff --git a/common-init.c b/common-init.c\nindex d26c9c1f20..4a3fa4d7be 100644\n--- a/common-init.c\n+++ b/common-init.c\n@@ -47,7 +47,7 @@ static void setup_environment(void)\n \tupdate_ref_namespace(NAMESPACE_REPLACE, git_replace_ref_base);\n \n \tif (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n }\n \n void init_git(const char **argv)\ndiff --git a/git.c b/git.c\nindex e5f1811b6b..f3ad3aad96 100644\n--- a/git.c\n+++ b/git.c\n@@ -202,7 +202,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\n \t\t} else if (!strcmp(cmd, \"--no-lazy-fetch\")) {\n-\t\t\tfetch_if_missing = 0;\n+\t\t\tthe_repository->fetch_if_missing = 0;\n \t\t\tsetenv(NO_LAZY_FETCH_ENVIRONMENT, \"1\", 1);\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\ndiff --git a/midx-write.c b/midx-write.c\nindex 580724d21a..8537102254 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -865,7 +865,7 @@ static void find_commits_for_midx_bitmap(struct commit_stack *commits,\n \t * complain later that we don't have reachability closure (and fail\n \t * appropriately).\n \t */\n-\tfetch_if_missing = 0;\n+\tctx->repo->fetch_if_missing = 0;\n \trevs.exclude_promisor_objects = 1;\n \n \tif (prepare_revision_walk(&revs))\ndiff --git a/odb.c b/odb.c\nindex dabd481f57..110326f063 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -528,8 +528,6 @@ void disable_obj_read_lock(void)\n \tpthread_mutex_destroy(&obj_read_mutex);\n }\n \n-int fetch_if_missing = 1;\n-\n static int register_all_submodule_sources(struct object_database *odb)\n {\n \tint ret = odb->submodule_source_paths.nr;\n@@ -595,7 +593,7 @@ static int do_oid_object_info_extended(struct object_database *odb,\n \t\t\tcontinue;\n \n \t\t/* Check if it is a missing object */\n-\t\tif (fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n+\t\tif (odb->repo->fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n \t\t    !already_retried &&\n \t\t    !(flags & OBJECT_INFO_SKIP_FETCH_OBJECT)) {\n \t\t\tpromisor_remote_get_direct(odb->repo, real, 1);\ndiff --git a/odb.h b/odb.h\nindex cbc2f9ced4..d3a1e378b6 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -15,14 +15,6 @@ struct repository;\n struct strbuf;\n struct strvec;\n \n-/*\n- * Set this to 0 to prevent odb_read_object_info_extended() from fetching missing\n- * blobs. This has a difference only if extensions.partialClone is set.\n- *\n- * Its default value is 1.\n- */\n-extern int fetch_if_missing;\n-\n /*\n  * Compute the exact path an alternate is at and returns it. In case of\n  * error NULL is returned and the human readable error is added to `err`\ndiff --git a/repository.c b/repository.c\nindex 651b0f6933..c2d954cf83 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -74,6 +74,7 @@ void initialize_repository(struct repository *repo)\n \tindex_state_init(repo->index, repo);\n \trepo->check_deprecated_config = true;\n \trepo->bare_cfg = -1;\n+\trepo->fetch_if_missing = 1;\n \trepo_config_values_init(&repo->config_values_private_);\n \n \t/*\ndiff --git a/repository.h b/repository.h\nindex 3b467a2513..11f5c2ed10 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -184,6 +184,12 @@ struct repository {\n \t/* True if commit-graph has been disabled within this process. */\n \tint commit_graph_disabled;\n \n+\t/*\n+\t * Controls whether the repository should lazily fetch missing\n+\t * objects from promisor remotes. Defaults to 1.\n+\t */\n+\tint fetch_if_missing;\n+\n \t/*\n \t * Lazily-populated cache mapping hook event names to configured hooks.\n \t * NULL until first hook use.\ndiff --git a/revision.c b/revision.c\nindex 526bcf3fb5..40cb1cc828 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2732,7 +2732,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t\trevs->ignore_missing = 1;\n \t} else if (opt && opt->allow_exclude_promisor_objects &&\n \t\t   !strcmp(arg, \"--exclude-promisor-objects\")) {\n-\t\tif (fetch_if_missing)\n+\t\tif (revs->repo->fetch_if_missing)\n \t\t\tBUG(\"exclude_promisor_objects can only be used when fetch_if_missing is 0\");\n \t\trevs->exclude_promisor_objects = 1;\n \t} else {\n-- \n2.43.0\n\n"},{"id":"550616","messageId":"xmqq7blsrclt.fsf@gitster.g","threadId":"66000","inReplyTo":"20260814072419.1666358-2-cat@malon.dev","subject":"Re: [PATCH v5 1/2] pack-objects: give fetch_if_missing call sites access to 'repo'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-14T14:38:38Z","receivedAt":"2026-08-14T14:38:43Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> 'option_parse_missing_action()' is registered as an OPT_CALLBACK, so\n> its signature is fixed and cannot easily gain a 'repo' parameter of\n> its own.\n\nIs that true?  OPT_CALLBACK takes (short, long, value, arghelp,\nhelp, callback), but the value parameter, which typically receives\nthe location to store the parsed value in, is NULL for\n\"--missing=<...>\" option, like so:\n\n\t\tOPT_CALLBACK_F(0, \"missing\", NULL, N_(\"action\"),\n\t\t  N_(\"handling for missing objects\"), PARSE_OPT_NONEG,\n\t\t  option_parse_missing_action),\n\nAnd \"value\" does not have to be the exact location; we can pass\nthe address of the repository instance there, and the callback can\ndereference it to reach repo->fetch_if_missing member to make an\nassignment, if it wants to.\n\nAm I missing something?\n\n> Let it only record 'arg_missing_action'. Instead, apply the\n> side effect right after 'parse_options()' returns in\n> 'cmd_pack_objects()', where 'repo' is available.\n\nWhen I review a new iteration, I first apply and look at the\ndifference from the previous iteration before reading the new\niteration of patches or their proposed commit log messages, and I\ndid notice this business about \"arg_missing_action\" and wondering\nwhat it was about (which by the way is precisely I read the patch\nbefore the log message to ensure that the log message explains what\nI find unclear in the code).  Thanks for describing this.  But if we\nuse OPT_CALLBACK() to pass the location of the variable, this may no\nlonger be necessary, no?\n\n"},{"id":"550617","messageId":"xmqq33wgrcco.fsf@gitster.g","threadId":"66000","inReplyTo":"20260814072419.1666358-1-cat@malon.dev","subject":"Re: [PATCH v5 0/2] repository: move fetch_if_missing into struct repository","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-14T14:44:07Z","receivedAt":"2026-08-14T14:44:10Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n>  Documentation/technical/partial-clone.adoc |  2 +-\n>  builtin/fetch-pack.c                       |  6 +++---\n>  builtin/fsck.c                             |  6 +++---\n>  builtin/index-pack.c                       |  8 +++----\n>  builtin/pack-objects.c                     | 25 ++++++++++++----------\n>  builtin/prune.c                            |  2 +-\n>  builtin/rev-list.c                         | 17 ++++++++-------\n>  common-init.c                              |  2 +-\n>  git.c                                      |  2 +-\n>  midx-write.c                               |  2 +-\n>  odb.c                                      |  4 +---\n>  odb.h                                      |  8 -------\n>  repository.c                               |  1 +\n>  repository.h                               |  6 ++++++\n>  revision.c                                 |  2 +-\n>  15 files changed, 47 insertions(+), 46 deletions(-)\n\n$ git grep -e 'the_repository->fetch_if_missing' \\\n    builtin/{fetch-pack,fsck,index-pack,pack-objects,prune,rev-list}.c \\\n    common-init.c midx-write.c odb.c repository.c revision.c\ncommon-init.c:          the_repository->fetch_if_missing = 0;\n\nCompared to the previous round, there is no remaining reference to\nthe_repository's '.fetch_if_missing' member.  There is a reference\nin common-init.c:setup_environment() that reacts to the\nGIT_NO_LAZY_FETCH environment variable, but that does not count.\n\nMuch better.\n\nThanks.\n"},{"id":"550656","messageId":"0b5e76c2-4fa6-47ae-982c-f138cf343bd7@malon.dev","threadId":"66000","inReplyTo":"xmqq7blsrclt.fsf@gitster.g","subject":"Re: [PATCH v5 1/2] pack-objects: give fetch_if_missing call sites access to 'repo'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-08-15T06:31:54Z","receivedAt":"2026-08-15T06:32:01Z","isPatch":true,"body":"On 8/14/26 22:38, Junio C Hamano wrote:\n> Tian Yuchen <cat@malon.dev> writes:\n> \n>> 'option_parse_missing_action()' is registered as an OPT_CALLBACK, so\n>> its signature is fixed and cannot easily gain a 'repo' parameter of\n>> its own.\n> \n> Is that true?  OPT_CALLBACK takes (short, long, value, arghelp,\n> help, callback), but the value parameter, which typically receives\n> the location to store the parsed value in, is NULL for\n> \"--missing=<...>\" option, like so:\n> \n> \t\tOPT_CALLBACK_F(0, \"missing\", NULL, N_(\"action\"),\n> \t\t  N_(\"handling for missing objects\"), PARSE_OPT_NONEG,\n> \t\t  option_parse_missing_action),\n> \n> And \"value\" does not have to be the exact location; we can pass\n> the address of the repository instance there, and the callback can\n> dereference it to reach repo->fetch_if_missing member to make an\n> assignment, if it wants to.\n> \n> Am I missing something?\n> \n\nOh yeah indeed, 'value' can be used for passing repo. My mind went blank \nat that moment :(\n\n>> Let it only record 'arg_missing_action'. Instead, apply the\n>> side effect right after 'parse_options()' returns in\n>> 'cmd_pack_objects()', where 'repo' is available.\n> \n> When I review a new iteration, I first apply and look at the\n> difference from the previous iteration before reading the new\n> iteration of patches or their proposed commit log messages, and I\n> did notice this business about \"arg_missing_action\" and wondering\n> what it was about (which by the way is precisely I read the patch\n> before the log message to ensure that the log message explains what\n\n\n\n> I find unclear in the code).  Thanks for describing this.  But if we\n> use OPT_CALLBACK() to pass the location of the variable, this may no\n> longer be necessary, no?\n> \n\nWill change, thanks!\n\nRegards, yuchen\n\n"},{"id":"550657","messageId":"20260815064747.2196896-1-cat@malon.dev","threadId":"66000","inReplyTo":"20260814072419.1666358-1-cat@malon.dev","subject":"[PATCH v6] repository: move fetch_if_missing into struct repository","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-08-15T06:47:46Z","receivedAt":"2026-08-15T06:48:01Z","isPatch":true,"body":"The global variable 'fetch_if_missing' controls whether a missing\nobject check should prompt a lazy fetch from a promisor remote.\nIn order to continue the libification effort, move it into\n'struct repository' and initialize it to 1 by default to keep the\nprevious behavior.\n\nbuiltin/fetch-pack.c, builtin/fsck.c, and builtin/rev-list.c are\nentered via commands marked RUN_SETUP in git.c:commands[]. Their\n'repo' parameter is only NULL when '-h' is given outside of a\nrepository, in which case either show_usage_if_asked() or\nparse_options()'s own '-h' handling exits the process before\nreturning. We can therefore drop their UNUSED markers and assign\nto 'repo' directly.\n\nbuiltin/index-pack.c is entered via RUN_SETUP_GENTLY, so its\n'repo' pointer can be NULL any time it is run outside of a\nrepository, not only with '-h'. We keep a NULL check there and fall\nback to 'the_repository'.\n\nbuiltin/pack-objects.c needs two adjustments to make 'repo' reach\nevery 'fetch_if_missing' call site: 'read_stdin_packs()' now takes a\n'struct repository *'; 'option_parse_missing_action()', which\nis registered as an OPT_CALLBACK, receives a 'repo' through the\noption's 'value' field now.\n\nAdditionally, update the partial clone documentation to reflect\nthat this is now a per-repository flag.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\nChanges since v5:\n\n - for 'option_parse_missing_action' in builtin/pack-objects.c, pass in\n a repo instance via the option's 'value' field. Hence, the commit 1/2\n of the v5 iteration (pack-objects: give fetch_if_missing call sites\n access to 'repo') is no longer needed. Drop it.\n\n Documentation/technical/partial-clone.adoc |  2 +-\n builtin/fetch-pack.c                       |  6 ++--\n builtin/fsck.c                             |  6 ++--\n builtin/index-pack.c                       |  8 +++---\n builtin/pack-objects.c                     | 32 ++++++++++++----------\n builtin/prune.c                            |  2 +-\n builtin/rev-list.c                         | 17 ++++++------\n common-init.c                              |  2 +-\n git.c                                      |  2 +-\n midx-write.c                               |  2 +-\n odb.c                                      |  4 +--\n odb.h                                      |  8 ------\n repository.c                               |  1 +\n repository.h                               |  6 ++++\n revision.c                                 |  2 +-\n 15 files changed, 51 insertions(+), 49 deletions(-)\n\ndiff --git a/Documentation/technical/partial-clone.adoc b/Documentation/technical/partial-clone.adoc\nindex e513e391ea..18718a3840 100644\n--- a/Documentation/technical/partial-clone.adoc\n+++ b/Documentation/technical/partial-clone.adoc\n@@ -159,7 +159,7 @@ and prefetch those objects in bulk.\n - `repack` in GC has been updated to not touch promisor packfiles at all,\n   and to only repack other objects.\n \n-- The global variable \"fetch_if_missing\" is used to control whether an\n+- The per-repository flag \"fetch_if_missing\" is used to control whether an\n   object lookup will attempt to dynamically fetch a missing object or\n   report an error.\n +\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 316badd969..86754296fa 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -49,7 +49,7 @@ static void add_sought_entry(struct ref ***sought, int *nr, int *alloc,\n int cmd_fetch_pack(int argc,\n \t\t   const char **argv,\n \t\t   const char *prefix UNUSED,\n-\t\t   struct repository *repo UNUSED)\n+\t\t   struct repository *repo)\n {\n \tint i, ret;\n \tstruct ref *fetched_refs = NULL, *remote_refs = NULL;\n@@ -67,8 +67,6 @@ int cmd_fetch_pack(int argc,\n \tstruct packet_reader reader;\n \tenum protocol_version version;\n \n-\tfetch_if_missing = 0;\n-\n \tpacket_trace_identity(\"fetch-pack\");\n \n \tmemset(&args, 0, sizeof(args));\n@@ -77,6 +75,8 @@ int cmd_fetch_pack(int argc,\n \n \tshow_usage_if_asked(argc, argv, fetch_pack_usage);\n \n+\trepo->fetch_if_missing = 0;\n+\n \tfor (i = 1; i < argc && *argv[i] == '-'; i++) {\n \t\tconst char *arg = argv[i];\n \ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex a6c054e45b..8cfc0e8b26 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -1017,15 +1017,15 @@ int cmd_fsck(int argc,\n \t\t.ref = NULL\n \t};\n \n-\t/* fsck knows how to handle missing promisor objects */\n-\tfetch_if_missing = 0;\n-\n \terrors_found = 0;\n \tdisable_replace_refs();\n \tsave_commit_buffer = 0;\n \n \targc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);\n \n+\t/* fsck knows how to handle missing promisor objects */\n+\trepo->fetch_if_missing = 0;\n+\n \tfsck_options_init(&fsck_walk_options, repo, FSCK_OPTIONS_DEFAULT);\n \tfsck_walk_options.walk = mark_object;\n \ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex bc86925ad0..28f8d01e04 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1886,7 +1886,7 @@ static void repack_local_links(void)\n int cmd_index_pack(int argc,\n \t\t   const char **argv,\n \t\t   const char *prefix,\n-\t\t   struct repository *repo UNUSED)\n+\t\t   struct repository *repo)\n {\n \tint i, fix_thin_pack = 0, verify = 0, stat_only = 0, rev_index;\n \tconst char *curr_index;\n@@ -1903,15 +1903,15 @@ int cmd_index_pack(int argc,\n \tint report_end_of_input = 0;\n \tint hash_algo = 0;\n \n+\tshow_usage_if_asked(argc, argv, index_pack_usage);\n+\n \t/*\n \t * index-pack never needs to fetch missing objects except when\n \t * REF_DELTA bases are missing (which are explicitly handled). It only\n \t * accesses the repo to do hash collision checks and to check which\n \t * REF_DELTA bases need to be fetched.\n \t */\n-\tfetch_if_missing = 0;\n-\n-\tshow_usage_if_asked(argc, argv, index_pack_usage);\n+\t(repo ? repo : the_repository)->fetch_if_missing = 0;\n \n \tdisable_replace_refs();\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 1ec5b6f206..cb1bd39aee 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -4087,9 +4087,10 @@ static void stdin_packs_read_input(struct rev_info *revs,\n \n static void add_unreachable_loose_objects(struct rev_info *revs);\n \n-static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n+static void read_stdin_packs(struct repository *repo,\n+\t\t\t     enum stdin_packs_mode mode, int rev_list_unpacked)\n {\n-\tint prev_fetch_if_missing = fetch_if_missing;\n+\tint prev_fetch_if_missing = repo->fetch_if_missing;\n \tstruct rev_info revs;\n \n \t/*\n@@ -4097,9 +4098,9 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n \t * walk is best-effort though we don't want to perform backfill fetches\n \t * for them.\n \t */\n-\tfetch_if_missing = 0;\n+\trepo->fetch_if_missing = 0;\n \n-\trepo_init_revisions(the_repository, &revs, NULL);\n+\trepo_init_revisions(repo, &revs, NULL);\n \t/*\n \t * Use a revision walk to fill in the namehash of objects in the include\n \t * packs. To save time, we'll avoid traversing through objects that are\n@@ -4145,7 +4146,7 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)\n \ttrace2_data_intmax(\"pack-objects\", the_repository, \"stdin_packs_hints\",\n \t\t\t   stdin_packs_hints_nr);\n \n-\tfetch_if_missing = prev_fetch_if_missing;\n+\trepo->fetch_if_missing = prev_fetch_if_missing;\n }\n \n static void add_cruft_object_entry(const struct object_id *oid, enum object_type type,\n@@ -4455,9 +4456,11 @@ static void show_object__ma_allow_promisor(struct object *obj, const char *name,\n \tshow_object(obj, name, data);\n }\n \n-static int option_parse_missing_action(const struct option *opt UNUSED,\n+static int option_parse_missing_action(const struct option *opt,\n \t\t\t\t       const char *arg, int unset)\n {\n+\tstruct repository *repo = opt->value;\n+\n \tassert(arg);\n \tassert(!unset);\n \n@@ -4469,14 +4472,14 @@ static int option_parse_missing_action(const struct option *opt UNUSED,\n \n \tif (!strcmp(arg, \"allow-any\")) {\n \t\targ_missing_action = MA_ALLOW_ANY;\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\tfn_show_object = show_object__ma_allow_any;\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(arg, \"allow-promisor\")) {\n \t\targ_missing_action = MA_ALLOW_PROMISOR;\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\tfn_show_object = show_object__ma_allow_promisor;\n \t\treturn 0;\n \t}\n@@ -5118,7 +5121,7 @@ static int parse_stdin_packs_mode(const struct option *opt, const char *arg,\n int cmd_pack_objects(int argc,\n \t\t     const char **argv,\n \t\t     const char *prefix,\n-\t\t     struct repository *repo UNUSED)\n+\t\t     struct repository *repo)\n {\n \tint use_internal_rev_list = 0;\n \tint all_progress_implied = 0;\n@@ -5225,7 +5228,7 @@ int cmd_pack_objects(int argc,\n \t\t\t      N_(\"write a bitmap index if possible\"),\n \t\t\t      WRITE_BITMAP_QUIET, PARSE_OPT_HIDDEN),\n \t\tOPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),\n-\t\tOPT_CALLBACK_F(0, \"missing\", NULL, N_(\"action\"),\n+\t\tOPT_CALLBACK_F(0, \"missing\", repo, N_(\"action\"),\n \t\t  N_(\"handling for missing objects\"), PARSE_OPT_NONEG,\n \t\t  option_parse_missing_action),\n \t\tOPT_BOOL(0, \"exclude-promisor-objects\", &exclude_promisor_objects,\n@@ -5345,7 +5348,7 @@ int cmd_pack_objects(int argc,\n \t\t\t\t  exclude_promisor_objects_best_effort,\n \t\t\t\t  \"--exclude-promisor-objects-best-effort\");\n \tif (exclude_promisor_objects) {\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \n \t\t/* --stdin-packs handles promisor objects separately. */\n \t\tif (!stdin_packs) {\n@@ -5354,8 +5357,9 @@ int cmd_pack_objects(int argc,\n \t\t}\n \t} else if (exclude_promisor_objects_best_effort) {\n \t\tuse_internal_rev_list = 1;\n-\t\tfetch_if_missing = 0;\n-\t\toption_parse_missing_action(NULL, \"allow-any\", 0);\n+\t\targ_missing_action = MA_ALLOW_ANY;\n+\t\trepo->fetch_if_missing = 0;\n+\t\tfn_show_object = show_object__ma_allow_any;\n \t\t/* revs configured below */\n \t}\n \tif (unpack_unreachable || keep_unreachable || pack_loose_unreachable)\n@@ -5471,7 +5475,7 @@ int cmd_pack_objects(int argc,\n \t\tprogress_state = start_progress(the_repository,\n \t\t\t\t\t\t_(\"Enumerating objects\"), 0);\n \tif (stdin_packs) {\n-\t\tread_stdin_packs(stdin_packs, rev_list_unpacked);\n+\t\tread_stdin_packs(repo, stdin_packs, rev_list_unpacked);\n \t} else if (cruft) {\n \t\tread_cruft_objects();\n \t} else if (!use_internal_rev_list) {\ndiff --git a/builtin/prune.c b/builtin/prune.c\nindex 55635a891f..a7e4678d11 100644\n--- a/builtin/prune.c\n+++ b/builtin/prune.c\n@@ -194,7 +194,7 @@ int cmd_prune(int argc,\n \tif (show_progress == -1)\n \t\tshow_progress = isatty(2);\n \tif (exclude_promisor_objects) {\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\trevs.exclude_promisor_objects = 1;\n \t}\n \ndiff --git a/builtin/rev-list.c b/builtin/rev-list.c\nindex 02818b81c6..6b596231ab 100644\n--- a/builtin/rev-list.c\n+++ b/builtin/rev-list.c\n@@ -500,7 +500,8 @@ static void print_disk_usage(off_t size)\n \tstrbuf_release(&sb);\n }\n \n-static inline int parse_missing_action_value(const char *value)\n+static inline int parse_missing_action_value(struct repository *repo,\n+\t\t\t\t\t     const char *value)\n {\n \tif (!strcmp(value, \"error\")) {\n \t\targ_missing_action = MA_ERROR;\n@@ -509,25 +510,25 @@ static inline int parse_missing_action_value(const char *value)\n \n \tif (!strcmp(value, \"allow-any\")) {\n \t\targ_missing_action = MA_ALLOW_ANY;\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"print\")) {\n \t\targ_missing_action = MA_PRINT;\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"print-info\")) {\n \t\targ_missing_action = MA_PRINT_INFO;\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n \tif (!strcmp(value, \"allow-promisor\")) {\n \t\targ_missing_action = MA_ALLOW_PROMISOR;\n-\t\tfetch_if_missing = 0;\n+\t\trepo->fetch_if_missing = 0;\n \t\treturn 1;\n \t}\n \n@@ -692,7 +693,7 @@ static void prepare_maximal_independent(struct rev_info *revs)\n int cmd_rev_list(int argc,\n \t\t const char **argv,\n \t\t const char *prefix,\n-\t\t struct repository *repo UNUSED)\n+\t\t struct repository *repo)\n {\n \tstruct rev_info revs;\n \tstruct rev_list_info info;\n@@ -745,10 +746,10 @@ int cmd_rev_list(int argc,\n \tfor (i = 1; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (!strcmp(arg, \"--exclude-promisor-objects\")) {\n-\t\t\tfetch_if_missing = 0;\n+\t\t\trepo->fetch_if_missing = 0;\n \t\t\trevs.exclude_promisor_objects = 1;\n \t\t} else if (skip_prefix(arg, \"--missing=\", &arg)) {\n-\t\t\tparse_missing_action_value(arg);\n+\t\t\tparse_missing_action_value(repo, arg);\n \t\t} else if (!strcmp(arg, \"-z\")) {\n \t\t\tline_term = '\\0';\n \t\t\tinfo_term = '\\0';\ndiff --git a/common-init.c b/common-init.c\nindex d26c9c1f20..4a3fa4d7be 100644\n--- a/common-init.c\n+++ b/common-init.c\n@@ -47,7 +47,7 @@ static void setup_environment(void)\n \tupdate_ref_namespace(NAMESPACE_REPLACE, git_replace_ref_base);\n \n \tif (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0))\n-\t\tfetch_if_missing = 0;\n+\t\tthe_repository->fetch_if_missing = 0;\n }\n \n void init_git(const char **argv)\ndiff --git a/git.c b/git.c\nindex e5f1811b6b..f3ad3aad96 100644\n--- a/git.c\n+++ b/git.c\n@@ -202,7 +202,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\n \t\t} else if (!strcmp(cmd, \"--no-lazy-fetch\")) {\n-\t\t\tfetch_if_missing = 0;\n+\t\t\tthe_repository->fetch_if_missing = 0;\n \t\t\tsetenv(NO_LAZY_FETCH_ENVIRONMENT, \"1\", 1);\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\ndiff --git a/midx-write.c b/midx-write.c\nindex 580724d21a..8537102254 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -865,7 +865,7 @@ static void find_commits_for_midx_bitmap(struct commit_stack *commits,\n \t * complain later that we don't have reachability closure (and fail\n \t * appropriately).\n \t */\n-\tfetch_if_missing = 0;\n+\tctx->repo->fetch_if_missing = 0;\n \trevs.exclude_promisor_objects = 1;\n \n \tif (prepare_revision_walk(&revs))\ndiff --git a/odb.c b/odb.c\nindex dabd481f57..110326f063 100644\n--- a/odb.c\n+++ b/odb.c\n@@ -528,8 +528,6 @@ void disable_obj_read_lock(void)\n \tpthread_mutex_destroy(&obj_read_mutex);\n }\n \n-int fetch_if_missing = 1;\n-\n static int register_all_submodule_sources(struct object_database *odb)\n {\n \tint ret = odb->submodule_source_paths.nr;\n@@ -595,7 +593,7 @@ static int do_oid_object_info_extended(struct object_database *odb,\n \t\t\tcontinue;\n \n \t\t/* Check if it is a missing object */\n-\t\tif (fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n+\t\tif (odb->repo->fetch_if_missing && repo_has_promisor_remote(odb->repo) &&\n \t\t    !already_retried &&\n \t\t    !(flags & OBJECT_INFO_SKIP_FETCH_OBJECT)) {\n \t\t\tpromisor_remote_get_direct(odb->repo, real, 1);\ndiff --git a/odb.h b/odb.h\nindex cbc2f9ced4..d3a1e378b6 100644\n--- a/odb.h\n+++ b/odb.h\n@@ -15,14 +15,6 @@ struct repository;\n struct strbuf;\n struct strvec;\n \n-/*\n- * Set this to 0 to prevent odb_read_object_info_extended() from fetching missing\n- * blobs. This has a difference only if extensions.partialClone is set.\n- *\n- * Its default value is 1.\n- */\n-extern int fetch_if_missing;\n-\n /*\n  * Compute the exact path an alternate is at and returns it. In case of\n  * error NULL is returned and the human readable error is added to `err`\ndiff --git a/repository.c b/repository.c\nindex 651b0f6933..c2d954cf83 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -74,6 +74,7 @@ void initialize_repository(struct repository *repo)\n \tindex_state_init(repo->index, repo);\n \trepo->check_deprecated_config = true;\n \trepo->bare_cfg = -1;\n+\trepo->fetch_if_missing = 1;\n \trepo_config_values_init(&repo->config_values_private_);\n \n \t/*\ndiff --git a/repository.h b/repository.h\nindex 3b467a2513..11f5c2ed10 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -184,6 +184,12 @@ struct repository {\n \t/* True if commit-graph has been disabled within this process. */\n \tint commit_graph_disabled;\n \n+\t/*\n+\t * Controls whether the repository should lazily fetch missing\n+\t * objects from promisor remotes. Defaults to 1.\n+\t */\n+\tint fetch_if_missing;\n+\n \t/*\n \t * Lazily-populated cache mapping hook event names to configured hooks.\n \t * NULL until first hook use.\ndiff --git a/revision.c b/revision.c\nindex 526bcf3fb5..40cb1cc828 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2732,7 +2732,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t\trevs->ignore_missing = 1;\n \t} else if (opt && opt->allow_exclude_promisor_objects &&\n \t\t   !strcmp(arg, \"--exclude-promisor-objects\")) {\n-\t\tif (fetch_if_missing)\n+\t\tif (revs->repo->fetch_if_missing)\n \t\t\tBUG(\"exclude_promisor_objects can only be used when fetch_if_missing is 0\");\n \t\trevs->exclude_promisor_objects = 1;\n \t} else {\n-- \n2.43.0\n\n"},{"id":"550665","messageId":"xmqqmrun8jeu.fsf@gitster.g","threadId":"66000","inReplyTo":"20260815064747.2196896-1-cat@malon.dev","subject":"Re: [PATCH v6] repository: move fetch_if_missing into struct repository","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-15T15:58:49Z","receivedAt":"2026-08-15T15:58:52Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> The global variable 'fetch_if_missing' controls whether a missing\n> object check should prompt a lazy fetch from a promisor remote.\n> In order to continue the libification effort, move it into\n> 'struct repository' and initialize it to 1 by default to keep the\n> previous behavior.\n>\n> builtin/fetch-pack.c, builtin/fsck.c, and builtin/rev-list.c are\n> entered via commands marked RUN_SETUP in git.c:commands[]. Their\n> 'repo' parameter is only NULL when '-h' is given outside of a\n> repository, in which case either show_usage_if_asked() or\n> parse_options()'s own '-h' handling exits the process before\n> returning. We can therefore drop their UNUSED markers and assign\n> to 'repo' directly.\n>\n> builtin/index-pack.c is entered via RUN_SETUP_GENTLY, so its\n> 'repo' pointer can be NULL any time it is run outside of a\n> repository, not only with '-h'. We keep a NULL check there and fall\n> back to 'the_repository'.\n\nHmph, are there legitimate situations where we run \"git index-pack\"\noutside a repository, or is it a user error?  Just being curious\nand not a suggestion to change the established behaviour (in other\nwords, even if it turns out that it should be diagnosed as a user\nerror, we shouldn't do so in the context of changing where the\nfetch_if_missing bit lives).\n\nOther than that, this iteration looks good to me.\n\n"},{"id":"550671","messageId":"59841e66-4361-484e-bc1f-131ea9b2d235@malon.dev","threadId":"66000","inReplyTo":"xmqqmrun8jeu.fsf@gitster.g","subject":"Re: [PATCH v6] repository: move fetch_if_missing into struct repository","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-08-16T06:20:50Z","receivedAt":"2026-08-16T06:21:01Z","isPatch":true,"body":"On 8/15/26 23:58, Junio C Hamano wrote:\n> Tian Yuchen <cat@malon.dev> writes:\n> \n>> The global variable 'fetch_if_missing' controls whether a missing\n>> object check should prompt a lazy fetch from a promisor remote.\n>> In order to continue the libification effort, move it into\n>> 'struct repository' and initialize it to 1 by default to keep the\n>> previous behavior.\n>>\n>> builtin/fetch-pack.c, builtin/fsck.c, and builtin/rev-list.c are\n>> entered via commands marked RUN_SETUP in git.c:commands[]. Their\n>> 'repo' parameter is only NULL when '-h' is given outside of a\n>> repository, in which case either show_usage_if_asked() or\n>> parse_options()'s own '-h' handling exits the process before\n>> returning. We can therefore drop their UNUSED markers and assign\n>> to 'repo' directly.\n>>\n>> builtin/index-pack.c is entered via RUN_SETUP_GENTLY, so its\n>> 'repo' pointer can be NULL any time it is run outside of a\n>> repository, not only with '-h'. We keep a NULL check there and fall\n>> back to 'the_repository'.\n> \n> Hmph, are there legitimate situations where we run \"git index-pack\"\n> outside a repository, or is it a user error?  Just being curious\n> and not a suggestion to change the established behaviour (in other\n> words, even if it turns out that it should be diagnosed as a user\n> error, we shouldn't do so in the context of changing where the\n> fetch_if_missing bit lives).\n> \n> Other than that, this iteration looks good to me.\n> \n\nInteresting topic. Refer to the git-index-pack documentation:\n\n...Reads a packed archive (.pack) from the specified file, builds a pack \nindex file (.idx) for it, and optionally writes a reverse-index (.rev) \nfor the specified pack. The packed archive, together with the pack \nindex, *can then be placed in the objects/pack/ directory of a Git \nrepository*...\n\nDoes this clarify that the design intent is to operate a standalone \n.pack file, and whether or not to put it into a repository are two \ndifferent things?\n\nThanks! yuchen\n\n"}]}