diff options
| author | Adrian Ratiu <adrian.ratiu@collabora.com> | 2026-01-12 20:46:30 +0200 |
|---|---|---|
| committer | Junio C Hamano <gitster@pobox.com> | 2026-01-12 11:56:56 -0800 |
| commit | 1685bba838ace8b4e325616ab914a6b01f18547f (patch) | |
| tree | 01a378dc363fc58f03a7564a9b633bfdabe81eda /submodule.c | |
| parent | 920fbe4d4ee8d4e191d33dde05a16ee0e74bdd44 (diff) | |
| download | git-1685bba838ace8b4e325616ab914a6b01f18547f.tar.xz | |
submodule: fix case-folding gitdir filesystem collisions
Add a new check when extension.submodulePathConfig is enabled, to
detect and prevent case-folding filesystem colisions. When this
new check is triggered, a stricter casefolding aware URI encoding
is used to percent-encode uppercase characters.
By using this check/retry mechanism the uppercase encoding is
only applied when necessary, so case-sensitive filesystems are
not affected.
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'submodule.c')
| -rw-r--r-- | submodule.c | 53 |
1 files changed, 52 insertions, 1 deletions
diff --git a/submodule.c b/submodule.c index 609d907377..a5a47359c7 100644 --- a/submodule.c +++ b/submodule.c @@ -2254,15 +2254,58 @@ out: return ret; } +static int check_casefolding_conflict(const char *git_dir, + const char *submodule_name, + const bool suffixes_match) +{ + char *p, *modules_dir = xstrdup(git_dir); + struct dirent *de; + DIR *dir = NULL; + int ret = 0; + + if ((p = find_last_dir_sep(modules_dir))) + *p = '\0'; + + /* No conflict is possible if modules_dir doesn't exist (first clone) */ + if (!is_directory(modules_dir)) + goto cleanup; + + dir = opendir(modules_dir); + if (!dir) { + ret = -1; + goto cleanup; + } + + /* Check for another directory under .git/modules that differs only in case. */ + while ((de = readdir(dir))) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + + if ((suffixes_match || is_git_directory(git_dir)) && + !strcasecmp(de->d_name, submodule_name) && + strcmp(de->d_name, submodule_name)) { + ret = -1; /* collision found */ + break; + } + } + +cleanup: + if (dir) + closedir(dir); + free(modules_dir); + return ret; +} + /* * Encoded gitdir validation, only used when extensions.submodulePathConfig is enabled. * This does not print errors like the non-encoded version, because encoding is supposed * to mitigate / fix all these. */ -static int validate_submodule_encoded_git_dir(char *git_dir, const char *submodule_name UNUSED) +static int validate_submodule_encoded_git_dir(char *git_dir, const char *submodule_name) { const char *modules_marker = "/modules/"; char *p = git_dir, *last_submodule_name = NULL; + int config_ignorecase = 0; if (!the_repository->repository_format_submodule_path_cfg) BUG("validate_submodule_encoded_git_dir() must be called with " @@ -2278,6 +2321,14 @@ static int validate_submodule_encoded_git_dir(char *git_dir, const char *submodu if (!last_submodule_name || strchr(last_submodule_name, '/')) return -1; + /* Prevent conflicts on case-folding filesystems */ + repo_config_get_bool(the_repository, "core.ignorecase", &config_ignorecase); + if (ignore_case || config_ignorecase) { + bool suffixes_match = !strcmp(last_submodule_name, submodule_name); + return check_casefolding_conflict(git_dir, submodule_name, + suffixes_match); + } + return 0; } |
