From e1339ebefbd25aedb20423782091511f749a6cf2 Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Mon, 15 Jun 2026 20:36:35 +0800 Subject: [PATCH 1/2] environment: move ignore_case into repo_config_values The 'core.ignorecase' configuration which is stored as the global variable 'ignore_case' acts as a core filesystem capability flag. Move this global variable into 'struct repo_config_values' to tie it to the specific repository instance it was read from. This reduces global state and aligns with the ongoing libification effort. Note that the newly introduced getter, 'repo_get_ignore_case()', intentionally avoids checking 'repo->gitdir'. This could safely accommodates early dynamic probing of the filesystem during 'git init' or clone operations, where the 'gitdir' might not be fully initialized but the filesystem capability must be recorded. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen --- environment.c | 8 ++++++++ environment.h | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/environment.c b/environment.c index fc3ed8bb1c7a66..c568d3b6fb0219 100644 --- a/environment.c +++ b/environment.c @@ -142,6 +142,13 @@ int is_bare_repository(void) return is_bare_repository_cfg && !repo_get_work_tree(the_repository); } +int repo_get_ignore_case(struct repository *repo) +{ + if (repo) + return repo_config_values(repo)->ignore_case; + return 0; +} + int have_git_dir(void) { return startup_info->have_repository @@ -720,5 +727,6 @@ void repo_config_values_init(struct repo_config_values *cfg) { cfg->attributes_file = NULL; cfg->apply_sparse_checkout = 0; + cfg->ignore_case = 0; cfg->branch_track = BRANCH_TRACK_REMOTE; } diff --git a/environment.h b/environment.h index 9eb97b3869c9b1..9e3d94fb803984 100644 --- a/environment.h +++ b/environment.h @@ -91,6 +91,7 @@ struct repo_config_values { /* section "core" config values */ char *attributes_file; int apply_sparse_checkout; + int ignore_case; /* section "branch" config values */ enum branch_track branch_track; @@ -123,6 +124,13 @@ int git_default_config(const char *, const char *, int git_default_core_config(const char *var, const char *value, const struct config_context *ctx, void *cb); +/* + * Getter for the `ignore_case` field of `struct repo_config_values`. + * It intentionally avoids checking `repo->gitdir` to allow early dynamic + * probing during `git init` or clone. + */ +int repo_get_ignore_case(struct repository *repo); + void repo_config_values_init(struct repo_config_values *cfg); /* From ceceb61b4a63f2e46c96085aa2369014353a2daa Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Mon, 15 Jun 2026 21:39:28 +0800 Subject: [PATCH 2/2] config: use repo_get_ignore_case() to access core.ignorecase Replace the accesses to the global 'ignore_case' variable with calls to 'repo_get_ignore_case(the_repository)'. This step eliminates the 'ignore_case' global state. Note on compat/win32/path-utils.c: To eliminate the global state, several helper functions (e.g. 'win32_fspathncmp()') now read from 'repo_get_ignore_case(the_repository)'. While this introduces dependency on 'repository.h' into the 'compat/', it avoids massive refactoring of the signatures across the codebase. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen --- apply.c | 2 +- builtin/fetch.c | 2 +- builtin/mv.c | 2 +- compat/win32/path-utils.c | 3 ++- dir.c | 18 +++++++++--------- environment.c | 3 +-- environment.h | 1 - fsmonitor.c | 2 +- name-hash.c | 6 +++--- read-cache.c | 6 +++--- refs/files-backend.c | 4 ++-- submodule.c | 2 +- t/helper/test-lazy-init-name-hash.c | 2 +- unpack-trees.c | 2 +- 14 files changed, 27 insertions(+), 28 deletions(-) diff --git a/apply.c b/apply.c index 249248d4f205ca..53309b9a090b5a 100644 --- a/apply.c +++ b/apply.c @@ -4008,7 +4008,7 @@ static int path_is_beyond_symlink_1(struct apply_state *state, struct strbuf *na struct cache_entry *ce; ce = index_file_exists(state->repo->index, name->buf, - name->len, ignore_case); + name->len, repo_get_ignore_case(the_repository)); if (ce && S_ISLNK(ce->ce_mode)) return 1; } else { diff --git a/builtin/fetch.c b/builtin/fetch.c index e4e8a72ed97120..67c7df0f3c6b26 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1819,7 +1819,7 @@ static void ref_transaction_rejection_handler(const char *refname, { struct ref_rejection_data *data = cb_data; - if (err == REF_TRANSACTION_ERROR_CASE_CONFLICT && ignore_case && + if (err == REF_TRANSACTION_ERROR_CASE_CONFLICT && repo_get_ignore_case(the_repository) && !data->case_sensitive_msg_shown) { error(_("You're on a case-insensitive filesystem, and the remote you are\n" "trying to fetch from has references that only differ in casing. It\n" diff --git a/builtin/mv.c b/builtin/mv.c index 948b3306390337..0f6f0600048a84 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -419,7 +419,7 @@ int cmd_mv(int argc, goto act_on_entry; } if (lstat(dst, &st) == 0 && - (!ignore_case || strcasecmp(src, dst))) { + (!repo_get_ignore_case(the_repository) || strcasecmp(src, dst))) { bad = _("destination exists"); if (force) { /* diff --git a/compat/win32/path-utils.c b/compat/win32/path-utils.c index 966ef779b9ca9b..4edb033e205c57 100644 --- a/compat/win32/path-utils.c +++ b/compat/win32/path-utils.c @@ -2,6 +2,7 @@ #include "../../git-compat-util.h" #include "../../environment.h" +#include "../../repository.h" int win32_has_dos_drive_prefix(const char *path) { @@ -75,7 +76,7 @@ int win32_fspathncmp(const char *a, const char *b, size_t count) } else if (is_dir_sep(*b)) return +1; - diff = ignore_case ? + diff = repo_get_ignore_case(the_repository) ? (unsigned char)tolower(*a) - (int)(unsigned char)tolower(*b) : (unsigned char)*a - (int)(unsigned char)*b; if (diff) diff --git a/dir.c b/dir.c index 33c81c256ee925..7116d65cad23ce 100644 --- a/dir.c +++ b/dir.c @@ -126,7 +126,7 @@ int count_slashes(const char *s) int git_fspathcmp(const char *a, const char *b) { - return ignore_case ? strcasecmp(a, b) : strcmp(a, b); + return repo_get_ignore_case(the_repository) ? strcasecmp(a, b) : strcmp(a, b); } int fspatheq(const char *a, const char *b) @@ -136,7 +136,7 @@ int fspatheq(const char *a, const char *b) int git_fspathncmp(const char *a, const char *b, size_t count) { - return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count); + return repo_get_ignore_case(the_repository) ? strncasecmp(a, b, count) : strncmp(a, b, count); } int paths_collide(const char *a, const char *b) @@ -153,7 +153,7 @@ int paths_collide(const char *a, const char *b) unsigned int fspathhash(const char *str) { - return ignore_case ? strihash(str) : strhash(str); + return repo_get_ignore_case(the_repository) ? strihash(str) : strhash(str); } int git_fnmatch(const struct pathspec_item *item, @@ -202,7 +202,7 @@ static int fnmatch_icase_mem(const char *pattern, int patternlen, use_str = str_buf.buf; } - if (ignore_case) + if (repo_get_ignore_case(the_repository)) flags |= WM_CASEFOLD; match_status = wildmatch(use_pat, use_str, flags); @@ -1851,7 +1851,7 @@ static struct dir_entry *dir_add_name(struct dir_struct *dir, struct index_state *istate, const char *pathname, int len) { - if (index_file_exists(istate, pathname, len, ignore_case)) + if (index_file_exists(istate, pathname, len, repo_get_ignore_case(the_repository))) return NULL; ALLOC_GROW(dir->entries, dir->nr+1, dir->internal.alloc); @@ -1888,7 +1888,7 @@ static enum exist_status directory_exists_in_index_icase(struct index_state *ist if (index_dir_exists(istate, dirname, len)) return index_directory; - ce = index_file_exists(istate, dirname, len, ignore_case); + ce = index_file_exists(istate, dirname, len, repo_get_ignore_case(the_repository)); if (ce && S_ISGITLINK(ce->ce_mode)) return index_gitdir; @@ -1907,7 +1907,7 @@ static enum exist_status directory_exists_in_index(struct index_state *istate, { int pos; - if (ignore_case) + if (repo_get_ignore_case(the_repository)) return directory_exists_in_index_icase(istate, dirname, len); pos = index_name_pos(istate, dirname, len); @@ -2447,7 +2447,7 @@ static enum path_treatment treat_path(struct dir_struct *dir, /* Always exclude indexed files */ has_path_in_index = !!index_file_exists(istate, path->buf, path->len, - ignore_case); + repo_get_ignore_case(the_repository)); if (dtype != DT_DIR && has_path_in_index) return path_none; @@ -3201,7 +3201,7 @@ static int cmp_icase(char a, char b) { if (a == b) return 0; - if (ignore_case) + if (repo_get_ignore_case(the_repository)) return toupper(a) - toupper(b); return a - b; } diff --git a/environment.c b/environment.c index c568d3b6fb0219..1f548b357cfd26 100644 --- a/environment.c +++ b/environment.c @@ -46,7 +46,6 @@ int trust_ctime = 1; int check_stat = 1; int has_symlinks = 1; int minimum_abbrev = 4, default_abbrev = -1; -int ignore_case; int assume_unchanged; int is_bare_repository_cfg = -1; /* unspecified */ int warn_on_object_refname_ambiguity = 1; @@ -342,7 +341,7 @@ int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.ignorecase")) { - ignore_case = git_config_bool(var, value); + cfg->ignore_case = git_config_bool(var, value); return 0; } diff --git a/environment.h b/environment.h index 9e3d94fb803984..66fdb1ed208c3d 100644 --- a/environment.h +++ b/environment.h @@ -171,7 +171,6 @@ extern int trust_ctime; extern int check_stat; extern int has_symlinks; extern int minimum_abbrev, default_abbrev; -extern int ignore_case; extern int assume_unchanged; extern int warn_on_object_refname_ambiguity; extern char *apply_default_whitespace; diff --git a/fsmonitor.c b/fsmonitor.c index d07dc18967ae33..5376e1987a9ceb 100644 --- a/fsmonitor.c +++ b/fsmonitor.c @@ -453,7 +453,7 @@ static void fsmonitor_refresh_callback(struct index_state *istate, char *name) * case-insensitive file system, try again using the name-hash * and dir-name-hash. */ - if (!nr_in_cone && ignore_case) { + if (!nr_in_cone && repo_get_ignore_case(the_repository)) { nr_in_cone = handle_using_name_hash_icase(istate, name); if (!nr_in_cone) nr_in_cone = handle_using_dir_name_hash_icase( diff --git a/name-hash.c b/name-hash.c index b91e276267891e..6bb2ecdd057cab 100644 --- a/name-hash.c +++ b/name-hash.c @@ -126,7 +126,7 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce) hashmap_add(&istate->name_hash, &ce->ent); } - if (ignore_case) + if (repo_get_ignore_case(the_repository)) add_dir_entry(istate, ce); } @@ -207,7 +207,7 @@ static int lookup_lazy_params(struct index_state *istate) * code to build the "istate->name_hash". We don't * need the complexity here. */ - if (!ignore_case) + if (!repo_get_ignore_case(the_repository)) return 0; nr_cpus = online_cpus(); @@ -651,7 +651,7 @@ void remove_name_hash(struct index_state *istate, struct cache_entry *ce) ce->ce_flags &= ~CE_HASHED; hashmap_remove(&istate->name_hash, &ce->ent, ce); - if (ignore_case) + if (repo_get_ignore_case(the_repository)) remove_dir_entry(istate, ce); } diff --git a/read-cache.c b/read-cache.c index 21829102ae275e..1409ac00b4572c 100644 --- a/read-cache.c +++ b/read-cache.c @@ -760,12 +760,12 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, * case of the file being added to the repository matches (is folded into) the existing * entry's directory case. */ - if (ignore_case) { + if (repo_get_ignore_case(the_repository)) { adjust_dirname_case(istate, ce->name); } if (!(flags & ADD_CACHE_RENORMALIZE)) { alias = index_file_exists(istate, ce->name, - ce_namelen(ce), ignore_case); + ce_namelen(ce), repo_get_ignore_case(the_repository)); if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) { @@ -786,7 +786,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, } else set_object_name_for_intent_to_add_entry(ce); - if (ignore_case && alias && different_name(ce, alias)) + if (repo_get_ignore_case(the_repository) && alias && different_name(ce, alias)) ce = create_alias_ce(istate, ce, alias); ce->ce_flags |= CE_ADDED; diff --git a/refs/files-backend.c b/refs/files-backend.c index a4c7858787127d..6d89d9817ae963 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -806,7 +806,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs, } else { unable_to_lock_message(ref_file.buf, myerr, err); if (myerr == EEXIST) { - if (ignore_case && + if (repo_get_ignore_case(the_repository) && transaction_has_case_conflicting_update(transaction, update)) { /* * In case-insensitive filesystems, ensure that conflicts within a @@ -920,7 +920,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs, * conflicts between 'foo' and 'Foo/bar'. So let's lowercase * the refname. */ - if (ignore_case) { + if (repo_get_ignore_case(the_repository)) { struct strbuf lower = STRBUF_INIT; strbuf_addstr(&lower, refname); diff --git a/submodule.c b/submodule.c index a939ff5072726f..32af85d967f4b7 100644 --- a/submodule.c +++ b/submodule.c @@ -2389,7 +2389,7 @@ static int validate_submodule_encoded_git_dir(char *git_dir, const char *submodu /* Prevent conflicts on case-folding filesystems */ repo_config_get_bool(the_repository, "core.ignorecase", &config_ignorecase); - if (ignore_case || config_ignorecase) { + if (repo_get_ignore_case(the_repository) || config_ignorecase) { bool suffixes_match = !strcmp(last_submodule_name, submodule_name); return check_casefolding_conflict(git_dir, submodule_name, suffixes_match); diff --git a/t/helper/test-lazy-init-name-hash.c b/t/helper/test-lazy-init-name-hash.c index e542985c943452..43cead6d7d5146 100644 --- a/t/helper/test-lazy-init-name-hash.c +++ b/t/helper/test-lazy-init-name-hash.c @@ -218,7 +218,7 @@ int cmd__lazy_init_name_hash(int argc, const char **argv) /* * istate->dir_hash is only created when ignore_case is set. */ - ignore_case = 1; + repo_config_values(the_repository)->ignore_case = 1; if (dump) { if (perf || analyze > 0) diff --git a/unpack-trees.c b/unpack-trees.c index 998a1e6dc70cae..330c5c01726e8b 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -2428,7 +2428,7 @@ static int check_ok_to_remove(const char *name, int len, int dtype, * * Ignore that lstat() if it matches. */ - if (ignore_case && icase_exists(o, name, len, st)) + if (repo_get_ignore_case(the_repository) && icase_exists(o, name, len, st)) return 0; if (o->internal.dir &&