#include "list-objects-filter-options.h"
#include "wildmatch.h"
#include "strbuf.h"
+#include "url.h"
#define OPT_QUIET (1 << 0)
#define OPT_CACHED (1 << 1)
{
struct strbuf gitdir_path = STRBUF_INIT;
+ /* Case 1: try the plain module name */
repo_git_path_append(the_repository, &gitdir_path, "modules/%s", submodule_name);
if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) {
strbuf_release(&gitdir_path);
return;
}
+ /* Case 2: Try URI-safe (RFC3986) encoding first, this fixes nested gitdirs */
+ strbuf_reset(&gitdir_path);
+ repo_git_path_append(the_repository, &gitdir_path, "modules/");
+ strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_rfc3986_unreserved);
+ if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) {
+ strbuf_release(&gitdir_path);
+ return;
+ }
+
+ /* Case 3: nothing worked, error out */
die(_("failed to set a valid default config for 'submodule.%s.gitdir'. "
"Please ensure it is set, for example by running something like: "
"'git config submodule.%s.gitdir .git/modules/%s'"),
#include "commit-reach.h"
#include "read-cache-ll.h"
#include "setup.h"
+#include "url.h"
static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
static int initialized_fetch_ref_tips;
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'",
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);
)
'
+test_expect_success 'setup submodules with nested git dirs' '
+ git init nested &&
+ test_commit -C nested nested &&
+ (
+ cd nested &&
+ cat >.gitmodules <<-EOF &&
+ [submodule "hippo"]
+ url = .
+ path = thing1
+ [submodule "hippo/hooks"]
+ url = .
+ path = thing2
+ EOF
+ git clone . thing1 &&
+ git clone . thing2 &&
+ git add .gitmodules thing1 thing2 &&
+ test_tick &&
+ git commit -m nested
+ )
+'
+
+test_expect_success 'git dirs of encoded sibling submodules must not be nested' '
+ git clone -c extensions.submodulePathConfig=true --recurse-submodules nested clone_nested &&
+
+ verify_submodule_gitdir_path clone_nested hippo modules/hippo &&
+ git -C clone_nested config submodule.hippo.gitdir > actual &&
+ test_grep "\.git/modules/hippo$" actual &&
+
+ verify_submodule_gitdir_path clone_nested hippo/hooks modules/hippo%2fhooks &&
+ git -C clone_nested config submodule.hippo/hooks.gitdir > actual &&
+ test_grep "\.git/modules/hippo%2fhooks$" actual
+'
+
+test_expect_success 'submodule git dir nesting detection must work with parallel cloning' '
+ git clone -c extensions.submodulePathConfig=true --recurse-submodules --jobs=2 nested clone_parallel &&
+
+ verify_submodule_gitdir_path clone_parallel hippo modules/hippo &&
+ git -C clone_nested config submodule.hippo.gitdir > actual &&
+ test_grep "\.git/modules/hippo$" actual &&
+
+ verify_submodule_gitdir_path clone_parallel hippo/hooks modules/hippo%2fhooks &&
+ git -C clone_nested config submodule.hippo/hooks.gitdir > actual &&
+ test_grep "\.git/modules/hippo%2fhooks$" actual
+'
+
+test_expect_success 'disabling extensions.submodulePathConfig prevents nested submodules' '
+ (
+ cd clone_nested &&
+ # disable extension and verify failure
+ git config --replace-all extensions.submodulePathConfig false &&
+ test_must_fail git submodule add ./thing2 hippo/foobar &&
+ # re-enable extension and verify it works
+ git config --replace-all extensions.submodulePathConfig true &&
+ git submodule add ./thing2 hippo/foobar
+ )
+'
+
test_done