aboutsummaryrefslogtreecommitdiff
path: root/submodule.c
diff options
context:
space:
mode:
authorAdrian Ratiu <adrian.ratiu@collabora.com>2026-01-12 20:46:29 +0200
committerJunio C Hamano <gitster@pobox.com>2026-01-12 11:56:56 -0800
commit920fbe4d4ee8d4e191d33dde05a16ee0e74bdd44 (patch)
tree7c34bcb0415a2e96f886b20e106bd244c6753b98 /submodule.c
parent226694bdf4aaecd18f6cd4df12656cc61b213d16 (diff)
downloadgit-920fbe4d4ee8d4e191d33dde05a16ee0e74bdd44.tar.xz
submodule--helper: fix filesystem collisions by encoding gitdir paths
Fix nested filesystem collisions by url-encoding gitdir paths stored in submodule.%s.gitdir, when extensions.submodulePathConfig is enabled. Credit goes to Junio and Patrick for coming up with this design: the encoding is only applied when necessary, to newly added submodules. Existing modules don't need the encoding because git already errors out when detecting nested gitdirs before this patch. This commit adds the basic url-encoding and some tests. Next commits extend the encode -> validate -> retry loop to fix more conflicts. Suggested-by: Junio C Hamano <gitster@pobox.com> Suggested-by: Patrick Steinhardt <ps@pks.im> 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.c42
1 files changed, 41 insertions, 1 deletions
diff --git a/submodule.c b/submodule.c
index f7af389c79..609d907377 100644
--- a/submodule.c
+++ b/submodule.c
@@ -32,6 +32,7 @@
#include "read-cache-ll.h"
#include "setup.h"
#include "advice.h"
+#include "url.h"
static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
static int initialized_fetch_ref_tips;
@@ -2253,12 +2254,43 @@ out:
return ret;
}
-int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
+/*
+ * 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)
+{
+ const char *modules_marker = "/modules/";
+ char *p = git_dir, *last_submodule_name = NULL;
+
+ if (!the_repository->repository_format_submodule_path_cfg)
+ BUG("validate_submodule_encoded_git_dir() must be called with "
+ "extensions.submodulePathConfig enabled.");
+
+ /* Find the last submodule name in the gitdir path (modules can be nested). */
+ while ((p = strstr(p, modules_marker))) {
+ last_submodule_name = p + strlen(modules_marker);
+ p++;
+ }
+
+ /* Prevent the use of '/' in encoded names */
+ if (!last_submodule_name || strchr(last_submodule_name, '/'))
+ return -1;
+
+ return 0;
+}
+
+static int validate_submodule_legacy_git_dir(char *git_dir, const char *submodule_name)
{
size_t len = strlen(git_dir), suffix_len = strlen(submodule_name);
char *p;
int ret = 0;
+ if (the_repository->repository_format_submodule_path_cfg)
+ BUG("validate_submodule_git_dir() must be called with "
+ "extensions.submodulePathConfig disabled.");
+
if (len <= suffix_len || (p = git_dir + len - suffix_len)[-1] != '/' ||
strcmp(p, submodule_name))
BUG("submodule name '%s' not a suffix of git dir '%s'",
@@ -2294,6 +2326,14 @@ int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
return 0;
}
+int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
+{
+ if (!the_repository->repository_format_submodule_path_cfg)
+ return validate_submodule_legacy_git_dir(git_dir, submodule_name);
+
+ return validate_submodule_encoded_git_dir(git_dir, submodule_name);
+}
+
int validate_submodule_path(const char *path)
{
char *p = xstrdup(path);