repository: create read_replace_refs setting

The 'read_replace_refs' global specifies whether or not we should respect the references of the form 'refs/replace/<oid>' to replace which object we look up when asking for '<oid>'. This global has caused issues when it is not initialized properly, such as in b6551feadfd (merge-tree: load default git config, 2023-05-10). To make this more robust, move its config-based initialization out of git_default_config and into prepare_repo_settings(). This provides a repository-scoped version of the 'read_replace_refs' global. The global still has its purpose: it is disabled process-wide by the GIT_NO_REPLACE_OBJECTS environment variable or by a call to disable_replace_refs() in some specific Git commands. Since we already encapsulated the use of the constant inside replace_refs_enabled(), we can perform the initialization inside that method, if necessary. This solves the problem of forgetting to check the config, as we will check it before returning this value. Due to this encapsulation, the global can move to be static within replace-object.c. There is an interesting behavior change possible here: we now have a repository-scoped understanding of this config value. Thus, if there was a command that recurses into submodules and might follow replace refs, then it would now respect the core.useReplaceRefs config value in each repository. 'git grep --recurse-submodules' is such a command that recurses into submodules in-process. We can demonstrate the granularity of this config value via a test in t7814. Signed-off-by: Derrick Stolee <derrickstolee@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Derrick Stolee committed Jun 6, 2023 at 13:24 UTC 9c7d1b057ff36ee4190621d46e9fe3c83377aea7
7 files changed +68 -16
config.c
-5
@@ -1838,11 +1838,6 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
1838 return 0;
1839 }
1840
1841 - if (!strcmp(var, "core.usereplacerefs")) {
1842 - read_replace_refs = git_config_bool(var, value);
1843 - return 0;
1844 - }
1845 -
1841 /* Add other config variables here and to Documentation/config.txt. */
1842 return platform_core_config(var, value, cb);
1843 }
environment.c
-1
@@ -63,7 +63,6 @@ const char *editor_program;
63 const char *askpass_program;
64 const char *excludes_file;
65 enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;
66 -int read_replace_refs = 1;
66 enum eol core_eol = EOL_UNSET;
67 int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;
68 char *check_roundtrip_encoding = "SHIFT-JIS";
replace-object.c
+18 -2
@@ -64,7 +64,7 @@ void prepare_replace_object(struct repository *r)
64 * replacement object's name (replaced recursively, if necessary).
65 * The return value is either oid or a pointer to a
66 * permanently-allocated value. This function always respects replace
67 - * references, regardless of the value of read_replace_refs.
67 + * references, regardless of the value of r->settings.read_replace_refs.
68 */
69 const struct object_id *do_lookup_replace_object(struct repository *r,
70 const struct object_id *oid)
@@ -85,6 +85,13 @@ const struct object_id *do_lookup_replace_object(struct repository *r,
85 die(_("replace depth too high for object %s"), oid_to_hex(oid));
86 }
87
88 +/*
89 + * This indicator determines whether replace references should be
90 + * respected process-wide, regardless of which repository is being
91 + * using at the time.
92 + */
93 +static int read_replace_refs = 1;
94 +
95 void disable_replace_refs(void)
96 {
97 read_replace_refs = 0;
@@ -92,5 +99,14 @@ void disable_replace_refs(void)
99
100 int replace_refs_enabled(struct repository *r)
101 {
95 - return read_replace_refs;
102 + if (!read_replace_refs)
103 + return 0;
104 +
105 + if (r->gitdir) {
106 + prepare_repo_settings(r);
107 + return r->settings.read_replace_refs;
108 + }
109 +
110 + /* repository has no objects or refs. */
111 + return 0;
112 }
replace-object.h
-8
@@ -5,14 +5,6 @@
5 #include "repository.h"
6 #include "object-store.h"
7
8 -/*
9 - * Do replace refs need to be checked this run? This variable is
10 - * initialized to true unless --no-replace-object is used or
11 - * $GIT_NO_REPLACE_OBJECTS is set, but is set to false by some
12 - * commands that do not want replace references to be active.
13 - */
14 -extern int read_replace_refs;
15 -
8 struct replace_object {
9 struct oidmap_entry original;
10 struct object_id replacement;
repo-settings.c
+1
@@ -67,6 +67,7 @@ void prepare_repo_settings(struct repository *r)
67 repo_cfg_bool(r, "pack.usebitmapboundarytraversal",
68 &r->settings.pack_use_bitmap_boundary_traversal,
69 r->settings.pack_use_bitmap_boundary_traversal);
70 + repo_cfg_bool(r, "core.usereplacerefs", &r->settings.read_replace_refs, 1);
71
72 /*
73 * The GIT_TEST_MULTI_PACK_INDEX variable is special in that
repository.h
+9
@@ -39,6 +39,15 @@ struct repo_settings {
39 int pack_read_reverse_index;
40 int pack_use_bitmap_boundary_traversal;
41
42 + /*
43 + * Does this repository have core.useReplaceRefs=true (on by
44 + * default)? This provides a repository-scoped version of this
45 + * config, though it could be disabled process-wide via some Git
46 + * builtins or the --no-replace-objects option. See
47 + * replace_refs_enabled() for more details.
48 + */
49 + int read_replace_refs;
50 +
51 struct fsmonitor_settings *fsmonitor; /* lazily loaded */
52
53 int index_version;
t/t7814-grep-recurse-submodules.sh
+40
@@ -594,4 +594,44 @@ test_expect_success 'grep partially-cloned submodule' '
594 )
595 '
596
597 +test_expect_success 'check scope of core.useReplaceRefs' '
598 + git init base &&
599 + git init base/sub &&
600 +
601 + echo A >base/a &&
602 + echo B >base/b &&
603 + echo C >base/sub/c &&
604 + echo D >base/sub/d &&
605 +
606 + git -C base/sub add c d &&
607 + git -C base/sub commit -m "Add files" &&
608 +
609 + git -C base submodule add ./sub &&
610 + git -C base add a b sub &&
611 + git -C base commit -m "Add files and submodule" &&
612 +
613 + A=$(git -C base rev-parse HEAD:a) &&
614 + B=$(git -C base rev-parse HEAD:b) &&
615 + C=$(git -C base/sub rev-parse HEAD:c) &&
616 + D=$(git -C base/sub rev-parse HEAD:d) &&
617 +
618 + git -C base replace $A $B &&
619 + git -C base/sub replace $C $D &&
620 +
621 + test_must_fail git -C base grep --cached --recurse-submodules A &&
622 + test_must_fail git -C base grep --cached --recurse-submodules C &&
623 +
624 + git -C base config core.useReplaceRefs false &&
625 + git -C base grep --recurse-submodules A &&
626 + test_must_fail git -C base grep --cached --recurse-submodules C &&
627 +
628 + git -C base/sub config core.useReplaceRefs false &&
629 + git -C base grep --cached --recurse-submodules A &&
630 + git -C base grep --cached --recurse-submodules C &&
631 +
632 + git -C base config --unset core.useReplaceRefs &&
633 + test_must_fail git -C base grep --cached --recurse-submodules A &&
634 + git -C base grep --cached --recurse-submodules C
635 +'
636 +
637 test_done