Merge branch 'ds/disable-replace-refs' into next

Introduce a mechanism to disable replace refs globally and per
repository.

* ds/disable-replace-refs:
  repository: create read_replace_refs setting
  replace-objects: create wrapper around setting
  repository: create disable_replace_refs()
  pack-bitmap.c: use commit boundary during bitmap traversal
  pack-bitmap.c: extract `fill_in_bitmap()`
  object: add object_array initializer helper function
This commit is contained in:
Junio C Hamano
2023-06-15 14:04:54 -07:00
28 changed files with 395 additions and 71 deletions

View File

@ -14,6 +14,9 @@ feature.experimental::
+
* `fetch.negotiationAlgorithm=skipping` may improve fetch negotiation times by
skipping more commits at a time, reducing the number of round trips.
+
* `pack.useBitmapBoundaryTraversal=true` may improve bitmap traversal times by
walking fewer objects.
feature.manyFiles::
Enable config options that optimize for repos with many files in the

View File

@ -123,6 +123,23 @@ pack.useBitmaps::
true. You should not generally need to turn this off unless
you are debugging pack bitmaps.
pack.useBitmapBoundaryTraversal::
When true, Git will use an experimental algorithm for computing
reachability queries with bitmaps. Instead of building up
complete bitmaps for all of the negated tips and then OR-ing
them together, consider negated tips with existing bitmaps as
additive (i.e. OR-ing them into the result if they exist,
ignoring them otherwise), and build up a bitmap at the boundary
instead.
+
When using this algorithm, Git may include too many objects as a result
of not opening up trees belonging to certain UNINTERESTING commits. This
inexactness matches the non-bitmap traversal algorithm.
+
In many cases, this can provide a speed-up over the exact algorithm,
particularly when there is poor bitmap coverage of the negated side of
the query.
pack.useSparse::
When true, git will default to using the '--sparse' option in
'git pack-objects' when the '--revs' option is present. This

View File

@ -804,7 +804,7 @@ static int batch_objects(struct batch_options *opt)
if (repo_has_promisor_remote(the_repository))
warning("This repository uses promisor remotes. Some objects may not be loaded.");
read_replace_refs = 0;
disable_replace_refs();
cb.opt = opt;
cb.expand = &data;

View File

@ -325,7 +325,7 @@ int cmd_commit_graph(int argc, const char **argv, const char *prefix)
git_config(git_default_config, NULL);
read_replace_refs = 0;
disable_replace_refs();
save_commit_buffer = 0;
argc = parse_options(argc, argv, prefix, options,

View File

@ -931,7 +931,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
fetch_if_missing = 0;
errors_found = 0;
read_replace_refs = 0;
disable_replace_refs();
save_commit_buffer = 0;
argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);

View File

@ -1753,7 +1753,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
if (argc == 2 && !strcmp(argv[1], "-h"))
usage(index_pack_usage);
read_replace_refs = 0;
disable_replace_refs();
fsck_options.walk = mark_link;
reset_pack_idx_option(&opts);

View File

@ -4284,7 +4284,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (DFS_NUM_STATES > (1 << OE_DFS_STATE_BITS))
BUG("too many dfs states, increase OE_DFS_STATE_BITS");
read_replace_refs = 0;
disable_replace_refs();
sparse = git_env_bool("GIT_TEST_PACK_SPARSE", -1);
if (the_repository->gitdir) {

View File

@ -165,7 +165,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
expire = TIME_MAX;
save_commit_buffer = 0;
read_replace_refs = 0;
disable_replace_refs();
repo_init_revisions(the_repository, &revs, prefix);
argc = parse_options(argc, argv, prefix, options, prune_usage, 0);

View File

@ -567,7 +567,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
OPT_END()
};
read_replace_refs = 0;
disable_replace_refs();
git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0);

View File

@ -608,7 +608,7 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix UNUSED)
int i;
struct object_id oid;
read_replace_refs = 0;
disable_replace_refs();
git_config(git_default_config, NULL);

View File

@ -36,7 +36,7 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
};
packet_trace_identity("upload-pack");
read_replace_refs = 0;
disable_replace_refs();
argc = parse_options(argc, argv, prefix, options, upload_pack_usage, 0);

View File

@ -29,6 +29,7 @@ linux-TEST-vars)
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master
export GIT_TEST_NO_WRITE_REV_INDEX=1
export GIT_TEST_CHECKOUT_WORKERS=2
export GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL=1
;;
linux-clang)
export GIT_TEST_DEFAULT_HASH=sha1

View File

@ -205,14 +205,12 @@ static struct commit_graph *alloc_commit_graph(void)
return g;
}
extern int read_replace_refs;
static int commit_graph_compatible(struct repository *r)
{
if (!r->gitdir)
return 0;
if (read_replace_refs) {
if (replace_refs_enabled(r)) {
prepare_replace_object(r);
if (hashmap_get_size(&r->objects->replace_map->map))
return 0;

View File

@ -1844,11 +1844,6 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
return 0;
}
if (!strcmp(var, "core.usereplacerefs")) {
read_replace_refs = git_config_bool(var, value);
return 0;
}
/* Add other config variables here and to Documentation/config.txt. */
return platform_core_config(var, value, cb);
}

View File

@ -63,7 +63,6 @@ const char *editor_program;
const char *askpass_program;
const char *excludes_file;
enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;
int read_replace_refs = 1;
enum eol core_eol = EOL_UNSET;
int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;
char *check_roundtrip_encoding = "SHIFT-JIS";
@ -184,7 +183,7 @@ void setup_git_env(const char *git_dir)
strvec_clear(&to_free);
if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
read_replace_refs = 0;
disable_replace_refs();
replace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);
git_replace_ref_base = xstrdup(replace_ref_base ? replace_ref_base
: "refs/replace/");

2
git.c
View File

@ -187,7 +187,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
if (envchanged)
*envchanged = 1;
} else if (!strcmp(cmd, "--no-replace-objects")) {
read_replace_refs = 0;
disable_replace_refs();
setenv(NO_REPLACE_OBJECTS_ENVIRONMENT, "1", 1);
if (envchanged)
*envchanged = 1;

View File

@ -158,7 +158,7 @@ static int add_ref_decoration(const char *refname, const struct object_id *oid,
if (starts_with(refname, git_replace_ref_base)) {
struct object_id original_oid;
if (!read_replace_refs)
if (!replace_refs_enabled(the_repository))
return 0;
if (get_oid_hex(refname + strlen(git_replace_ref_base),
&original_oid)) {

View File

@ -357,6 +357,12 @@ void object_list_free(struct object_list **list)
*/
static char object_array_slopbuf[1];
void object_array_init(struct object_array *array)
{
struct object_array blank = OBJECT_ARRAY_INIT;
memcpy(array, &blank, sizeof(*array));
}
void add_object_array_with_path(struct object *obj, const char *name,
struct object_array *array,
unsigned mode, const char *path)

View File

@ -58,6 +58,8 @@ struct object_array {
#define OBJECT_ARRAY_INIT { 0 }
void object_array_init(struct object_array *array);
/*
* object flag allocation:
* revision.h: 0---------10 15 23------27

View File

@ -1043,6 +1043,160 @@ static int add_commit_to_bitmap(struct bitmap_index *bitmap_git,
return 1;
}
static struct bitmap *fill_in_bitmap(struct bitmap_index *bitmap_git,
struct rev_info *revs,
struct bitmap *base,
struct bitmap *seen)
{
struct include_data incdata;
struct bitmap_show_data show_data;
if (!base)
base = bitmap_new();
incdata.bitmap_git = bitmap_git;
incdata.base = base;
incdata.seen = seen;
revs->include_check = should_include;
revs->include_check_obj = should_include_obj;
revs->include_check_data = &incdata;
if (prepare_revision_walk(revs))
die(_("revision walk setup failed"));
show_data.bitmap_git = bitmap_git;
show_data.base = base;
traverse_commit_list(revs, show_commit, show_object, &show_data);
revs->include_check = NULL;
revs->include_check_obj = NULL;
revs->include_check_data = NULL;
return base;
}
struct bitmap_boundary_cb {
struct bitmap_index *bitmap_git;
struct bitmap *base;
struct object_array boundary;
};
static void show_boundary_commit(struct commit *commit, void *_data)
{
struct bitmap_boundary_cb *data = _data;
if (commit->object.flags & BOUNDARY)
add_object_array(&commit->object, "", &data->boundary);
if (commit->object.flags & UNINTERESTING) {
if (bitmap_walk_contains(data->bitmap_git, data->base,
&commit->object.oid))
return;
add_commit_to_bitmap(data->bitmap_git, &data->base, commit);
}
}
static void show_boundary_object(struct object *object,
const char *name, void *data)
{
BUG("should not be called");
}
static struct bitmap *find_boundary_objects(struct bitmap_index *bitmap_git,
struct rev_info *revs,
struct object_list *roots)
{
struct bitmap_boundary_cb cb;
struct object_list *root;
unsigned int i;
unsigned int tmp_blobs, tmp_trees, tmp_tags;
int any_missing = 0;
cb.bitmap_git = bitmap_git;
cb.base = bitmap_new();
object_array_init(&cb.boundary);
revs->ignore_missing_links = 1;
/*
* OR in any existing reachability bitmaps among `roots` into
* `cb.base`.
*/
for (root = roots; root; root = root->next) {
struct object *object = root->item;
if (object->type != OBJ_COMMIT ||
bitmap_walk_contains(bitmap_git, cb.base, &object->oid))
continue;
if (add_commit_to_bitmap(bitmap_git, &cb.base,
(struct commit *)object))
continue;
any_missing = 1;
}
if (!any_missing)
goto cleanup;
tmp_blobs = revs->blob_objects;
tmp_trees = revs->tree_objects;
tmp_tags = revs->blob_objects;
revs->blob_objects = 0;
revs->tree_objects = 0;
revs->tag_objects = 0;
/*
* We didn't have complete coverage of the roots. First setup a
* revision walk to (a) OR in any bitmaps that are UNINTERESTING
* between the tips and boundary, and (b) record the boundary.
*/
trace2_region_enter("pack-bitmap", "boundary-prepare", the_repository);
if (prepare_revision_walk(revs))
die("revision walk setup failed");
trace2_region_leave("pack-bitmap", "boundary-prepare", the_repository);
trace2_region_enter("pack-bitmap", "boundary-traverse", the_repository);
revs->boundary = 1;
traverse_commit_list_filtered(revs,
show_boundary_commit,
show_boundary_object,
&cb, NULL);
revs->boundary = 0;
trace2_region_leave("pack-bitmap", "boundary-traverse", the_repository);
revs->blob_objects = tmp_blobs;
revs->tree_objects = tmp_trees;
revs->tag_objects = tmp_tags;
reset_revision_walk();
clear_object_flags(UNINTERESTING);
/*
* Then add the boundary commit(s) as fill-in traversal tips.
*/
trace2_region_enter("pack-bitmap", "boundary-fill-in", the_repository);
for (i = 0; i < cb.boundary.nr; i++) {
struct object *obj = cb.boundary.objects[i].item;
if (bitmap_walk_contains(bitmap_git, cb.base, &obj->oid))
obj->flags |= SEEN;
else
add_pending_object(revs, obj, "");
}
if (revs->pending.nr)
cb.base = fill_in_bitmap(bitmap_git, revs, cb.base, NULL);
trace2_region_leave("pack-bitmap", "boundary-fill-in", the_repository);
cleanup:
object_array_clear(&cb.boundary);
revs->ignore_missing_links = 0;
return cb.base;
}
static struct bitmap *find_objects(struct bitmap_index *bitmap_git,
struct rev_info *revs,
struct object_list *roots,
@ -1109,33 +1263,19 @@ static struct bitmap *find_objects(struct bitmap_index *bitmap_git,
}
if (needs_walk) {
struct include_data incdata;
struct bitmap_show_data show_data;
if (!base)
base = bitmap_new();
incdata.bitmap_git = bitmap_git;
incdata.base = base;
incdata.seen = seen;
revs->include_check = should_include;
revs->include_check_obj = should_include_obj;
revs->include_check_data = &incdata;
if (prepare_revision_walk(revs))
die(_("revision walk setup failed"));
show_data.bitmap_git = bitmap_git;
show_data.base = base;
traverse_commit_list(revs,
show_commit, show_object,
&show_data);
revs->include_check = NULL;
revs->include_check_obj = NULL;
revs->include_check_data = NULL;
/*
* This fill-in traversal may walk over some objects
* again, since we have already traversed in order to
* find the boundary.
*
* But this extra walk should be extremely cheap, since
* all commit objects are loaded into memory, and
* because we skip walking to parents that are
* UNINTERESTING, since it will be marked in the haves
* bitmap already (or it has an on-disk bitmap, since
* OR-ing it in covers all of its ancestors).
*/
base = fill_in_bitmap(bitmap_git, revs, base, seen);
}
return base;
@ -1528,6 +1668,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
int filter_provided_objects)
{
unsigned int i;
int use_boundary_traversal;
struct object_list *wants = NULL;
struct object_list *haves = NULL;
@ -1578,13 +1719,21 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
object_list_insert(object, &wants);
}
/*
* if we have a HAVES list, but none of those haves is contained
* in the packfile that has a bitmap, we don't have anything to
* optimize here
*/
if (haves && !in_bitmapped_pack(bitmap_git, haves))
goto cleanup;
use_boundary_traversal = git_env_bool(GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL, -1);
if (use_boundary_traversal < 0) {
prepare_repo_settings(revs->repo);
use_boundary_traversal = revs->repo->settings.pack_use_bitmap_boundary_traversal;
}
if (!use_boundary_traversal) {
/*
* if we have a HAVES list, but none of those haves is contained
* in the packfile that has a bitmap, we don't have anything to
* optimize here
*/
if (haves && !in_bitmapped_pack(bitmap_git, haves))
goto cleanup;
}
/* if we don't want anything, we're done here */
if (!wants)
@ -1598,18 +1747,32 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
if (load_bitmap(revs->repo, bitmap_git) < 0)
goto cleanup;
object_array_clear(&revs->pending);
if (!use_boundary_traversal)
object_array_clear(&revs->pending);
if (haves) {
revs->ignore_missing_links = 1;
haves_bitmap = find_objects(bitmap_git, revs, haves, NULL);
reset_revision_walk();
revs->ignore_missing_links = 0;
if (use_boundary_traversal) {
trace2_region_enter("pack-bitmap", "haves/boundary", the_repository);
haves_bitmap = find_boundary_objects(bitmap_git, revs, haves);
trace2_region_leave("pack-bitmap", "haves/boundary", the_repository);
} else {
trace2_region_enter("pack-bitmap", "haves/classic", the_repository);
revs->ignore_missing_links = 1;
haves_bitmap = find_objects(bitmap_git, revs, haves, NULL);
reset_revision_walk();
revs->ignore_missing_links = 0;
trace2_region_leave("pack-bitmap", "haves/classic", the_repository);
}
if (!haves_bitmap)
BUG("failed to perform bitmap walk");
}
if (use_boundary_traversal) {
object_array_clear(&revs->pending);
reset_revision_walk();
}
wants_bitmap = find_objects(bitmap_git, revs, wants, haves_bitmap);
if (!wants_bitmap)

View File

@ -62,6 +62,10 @@ void traverse_bitmap_commit_list(struct bitmap_index *,
void test_bitmap_walk(struct rev_info *revs);
int test_bitmap_commits(struct repository *r);
int test_bitmap_hashes(struct repository *r);
#define GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL \
"GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL"
struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
int filter_provided_objects);
uint32_t midx_preferred_pack(struct bitmap_index *bitmap_git);

View File

@ -64,7 +64,7 @@ void prepare_replace_object(struct repository *r)
* replacement object's name (replaced recursively, if necessary).
* The return value is either oid or a pointer to a
* permanently-allocated value. This function always respects replace
* references, regardless of the value of read_replace_refs.
* references, regardless of the value of r->settings.read_replace_refs.
*/
const struct object_id *do_lookup_replace_object(struct repository *r,
const struct object_id *oid)
@ -84,3 +84,29 @@ const struct object_id *do_lookup_replace_object(struct repository *r,
}
die(_("replace depth too high for object %s"), oid_to_hex(oid));
}
/*
* This indicator determines whether replace references should be
* respected process-wide, regardless of which repository is being
* using at the time.
*/
static int read_replace_refs = 1;
void disable_replace_refs(void)
{
read_replace_refs = 0;
}
int replace_refs_enabled(struct repository *r)
{
if (!read_replace_refs)
return 0;
if (r->gitdir) {
prepare_repo_settings(r);
return r->settings.read_replace_refs;
}
/* repository has no objects or refs. */
return 0;
}

View File

@ -5,14 +5,6 @@
#include "repository.h"
#include "object-store-ll.h"
/*
* Do replace refs need to be checked this run? This variable is
* initialized to true unless --no-replace-object is used or
* $GIT_NO_REPLACE_OBJECTS is set, but is set to false by some
* commands that do not want replace references to be active.
*/
extern int read_replace_refs;
struct replace_object {
struct oidmap_entry original;
struct object_id replacement;
@ -27,6 +19,18 @@ void prepare_replace_object(struct repository *r);
const struct object_id *do_lookup_replace_object(struct repository *r,
const struct object_id *oid);
/*
* Some commands disable replace-refs unconditionally, and otherwise each
* repository could alter the core.useReplaceRefs config value.
*
* Return 1 if and only if all of the following are true:
*
* a. disable_replace_refs() has not been called.
* b. GIT_NO_REPLACE_OBJECTS is unset or zero.
* c. the given repository does not have core.useReplaceRefs=false.
*/
int replace_refs_enabled(struct repository *r);
/*
* If object sha1 should be replaced, return the replacement object's
* name (replaced recursively, if necessary). The return value is
@ -41,11 +45,19 @@ const struct object_id *do_lookup_replace_object(struct repository *r,
static inline const struct object_id *lookup_replace_object(struct repository *r,
const struct object_id *oid)
{
if (!read_replace_refs ||
if (!replace_refs_enabled(r) ||
(r->objects->replace_map_initialized &&
r->objects->replace_map->map.tablesize == 0))
return oid;
return do_lookup_replace_object(r, oid);
}
/*
* Some commands override config and environment settings for using
* replace references. Use this method to disable the setting and ensure
* those other settings will not override this choice. This applies
* globally to all in-process repositories.
*/
void disable_replace_refs(void);
#endif /* REPLACE_OBJECT_H */

View File

@ -41,8 +41,10 @@ void prepare_repo_settings(struct repository *r)
repo_cfg_bool(r, "feature.experimental", &experimental, 0);
/* Defaults modified by feature.* */
if (experimental)
if (experimental) {
r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;
r->settings.pack_use_bitmap_boundary_traversal = 1;
}
if (manyfiles) {
r->settings.index_version = 4;
r->settings.index_skip_hash = 1;
@ -62,6 +64,10 @@ void prepare_repo_settings(struct repository *r)
repo_cfg_bool(r, "index.sparse", &r->settings.sparse_index, 0);
repo_cfg_bool(r, "index.skiphash", &r->settings.index_skip_hash, r->settings.index_skip_hash);
repo_cfg_bool(r, "pack.readreverseindex", &r->settings.pack_read_reverse_index, 1);
repo_cfg_bool(r, "pack.usebitmapboundarytraversal",
&r->settings.pack_use_bitmap_boundary_traversal,
r->settings.pack_use_bitmap_boundary_traversal);
repo_cfg_bool(r, "core.usereplacerefs", &r->settings.read_replace_refs, 1);
/*
* The GIT_TEST_MULTI_PACK_INDEX variable is special in that

View File

@ -35,6 +35,16 @@ struct repo_settings {
int command_requires_full_index;
int sparse_index;
int pack_read_reverse_index;
int pack_use_bitmap_boundary_traversal;
/*
* Does this repository have core.useReplaceRefs=true (on by
* default)? This provides a repository-scoped version of this
* config, though it could be disabled process-wide via some Git
* builtins or the --no-replace-objects option. See
* replace_refs_enabled() for more details.
*/
int read_replace_refs;
struct fsmonitor_settings *fsmonitor; /* lazily loaded */

View File

@ -442,6 +442,10 @@ GIT_TEST_INDEX_VERSION=<n> exercises the index read/write code path
for the index version specified. Can be set to any valid version
(currently 2, 3, or 4).
GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL=<boolean> if enabled will
use the boundary-based bitmap traversal algorithm. See the documentation
of `pack.useBitmapBoundaryTraversal` for more details.
GIT_TEST_PACK_SPARSE=<boolean> if disabled will default the pack-objects
builtin to use the non-sparse object walk. This can still be overridden by
the --sparse command-line argument.

View File

@ -9,6 +9,10 @@ test_description='exercise basic bitmap functionality'
# their place.
GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
# Likewise, allow individual tests to control whether or not they use
# the boundary-based traversal.
sane_unset GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL
objpath () {
echo ".git/objects/$(echo "$1" | sed -e 's|\(..\)|\1/|')"
}
@ -457,6 +461,13 @@ test_bitmap_cases () {
test_bitmap_cases
GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL=1
export GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL
test_bitmap_cases
sane_unset GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL
test_expect_success 'incremental repack fails when bitmaps are requested' '
test_commit more-1 &&
test_must_fail git repack -d 2>err &&
@ -468,6 +479,33 @@ test_expect_success 'incremental repack can disable bitmaps' '
git repack -d --no-write-bitmap-index
'
test_expect_success 'boundary-based traversal is used when requested' '
git repack -a -d --write-bitmap-index &&
for argv in \
"git -c pack.useBitmapBoundaryTraversal=true" \
"git -c feature.experimental=true" \
"GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL=1 git"
do
eval "GIT_TRACE2_EVENT=1 $argv rev-list --objects \
--use-bitmap-index second..other 2>perf" &&
grep "\"region_enter\".*\"label\":\"haves/boundary\"" perf ||
return 1
done &&
for argv in \
"git -c pack.useBitmapBoundaryTraversal=false" \
"git -c feature.experimental=true -c pack.useBitmapBoundaryTraversal=false" \
"GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL=0 git -c pack.useBitmapBoundaryTraversal=true" \
"GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL=0 git -c feature.experimental=true"
do
eval "GIT_TRACE2_EVENT=1 $argv rev-list --objects \
--use-bitmap-index second..other 2>perf" &&
grep "\"region_enter\".*\"label\":\"haves/classic\"" perf ||
return 1
done
'
test_bitmap_cases "pack.writeBitmapLookupTable"
test_expect_success 'verify writing bitmap lookup table when enabled' '

View File

@ -594,4 +594,44 @@ test_expect_success 'grep partially-cloned submodule' '
)
'
test_expect_success 'check scope of core.useReplaceRefs' '
git init base &&
git init base/sub &&
echo A >base/a &&
echo B >base/b &&
echo C >base/sub/c &&
echo D >base/sub/d &&
git -C base/sub add c d &&
git -C base/sub commit -m "Add files" &&
git -C base submodule add ./sub &&
git -C base add a b sub &&
git -C base commit -m "Add files and submodule" &&
A=$(git -C base rev-parse HEAD:a) &&
B=$(git -C base rev-parse HEAD:b) &&
C=$(git -C base/sub rev-parse HEAD:c) &&
D=$(git -C base/sub rev-parse HEAD:d) &&
git -C base replace $A $B &&
git -C base/sub replace $C $D &&
test_must_fail git -C base grep --cached --recurse-submodules A &&
test_must_fail git -C base grep --cached --recurse-submodules C &&
git -C base config core.useReplaceRefs false &&
git -C base grep --recurse-submodules A &&
test_must_fail git -C base grep --cached --recurse-submodules C &&
git -C base/sub config core.useReplaceRefs false &&
git -C base grep --cached --recurse-submodules A &&
git -C base grep --cached --recurse-submodules C &&
git -C base config --unset core.useReplaceRefs &&
test_must_fail git -C base grep --cached --recurse-submodules A &&
git -C base grep --cached --recurse-submodules C
'
test_done