submodule: extract path to submodule gitdir func

We currently store each submodule gitdir in ".git/modules/<name>", but this has problems with some submodule naming schemes, as described in a comment in submodule_name_to_gitdir() in this patch. Extract the determination of the location of a submodule's gitdir into its own function submodule_name_to_gitdir(). For now, the problem remains unsolved, but this puts us in a better position for finding a solution. This was motivated, at $DAYJOB, by a part of Android's repo hierarchy [1]. In particular, there is a repo "build", and several repos of the form "build/<name>". This is based on earlier work by Brandon Williams [2]. [1] https://android.googlesource.com/platform/ [2] https://lore.kernel.org/git/20180808223323.79989-2-bmwill@google.com/ Signed-off-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jonathan Tan committed Sep 15, 2021 at 11:59 UTC ce125d431aaa7a12623a81267a221f64552ffd17
5 files changed +72 -27
builtin/submodule--helper.c
+8 -2
@@ -1704,18 +1704,24 @@ static int add_possible_reference_from_superproject(
1704 * standard layout with .git/(modules/<name>)+/objects
1705 */
1706 if (strip_suffix(odb->path, "/objects", &len)) {
1707 + struct repository alternate;
1708 char *sm_alternate;
1709 struct strbuf sb = STRBUF_INIT;
1710 struct strbuf err = STRBUF_INIT;
1711 strbuf_add(&sb, odb->path, len);
1712
1713 + repo_init(&alternate, sb.buf, NULL);
1714 +
1715 /*
1716 * We need to end the new path with '/' to mark it as a dir,
1717 * otherwise a submodule name containing '/' will be broken
1718 * as the last part of a missing submodule reference would
1719 * be taken as a file name.
1720 */
1718 - strbuf_addf(&sb, "/modules/%s/", sas->submodule_name);
1721 + strbuf_reset(&sb);
1722 + submodule_name_to_gitdir(&sb, &alternate, sas->submodule_name);
1723 + strbuf_addch(&sb, '/');
1724 + repo_clear(&alternate);
1725
1726 sm_alternate = compute_alternate_path(sb.buf, &err);
1727 if (sm_alternate) {
@@ -1785,7 +1791,7 @@ static int clone_submodule(struct module_clone_data *clone_data)
1791 struct strbuf sb = STRBUF_INIT;
1792 struct child_process cp = CHILD_PROCESS_INIT;
1793
1788 - strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), clone_data->name);
1794 + submodule_name_to_gitdir(&sb, the_repository, clone_data->name);
1795 sm_gitdir = absolute_pathdup(sb.buf);
1796 strbuf_reset(&sb);
1797
dir.c
+1 -1
@@ -3633,7 +3633,7 @@ static void connect_wt_gitdir_in_nested(const char *sub_worktree,
3633 strbuf_reset(&sub_wt);
3634 strbuf_reset(&sub_gd);
3635 strbuf_addf(&sub_wt, "%s/%s", sub_worktree, sub->path);
3636 - strbuf_addf(&sub_gd, "%s/modules/%s", sub_gitdir, sub->name);
3636 + submodule_name_to_gitdir(&sub_gd, &subrepo, sub->name);
3637
3638 connect_work_tree_and_git_dir(sub_wt.buf, sub_gd.buf, 1);
3639 }
repository.c
+1 -2
@@ -213,8 +213,7 @@ int repo_submodule_init(struct repository *subrepo,
213 * submodule would not have a worktree.
214 */
215 strbuf_reset(&gitdir);
216 - strbuf_repo_git_path(&gitdir, superproject,
217 - "modules/%s", sub->name);
216 + submodule_name_to_gitdir(&gitdir, superproject, sub->name);
217
218 if (repo_init(subrepo, gitdir.buf, NULL)) {
219 ret = -1;
submodule.c
+55 -22
@@ -1819,14 +1819,16 @@ out:
1819
1820 void submodule_unset_core_worktree(const struct submodule *sub)
1821 {
1822 - char *config_path = xstrfmt("%s/modules/%s/config",
1823 - get_git_dir(), sub->name);
1822 + struct strbuf config_path = STRBUF_INIT;
1823
1825 - if (git_config_set_in_file_gently(config_path, "core.worktree", NULL))
1824 + submodule_name_to_gitdir(&config_path, the_repository, sub->name);
1825 + strbuf_addstr(&config_path, "/config");
1826 +
1827 + if (git_config_set_in_file_gently(config_path.buf, "core.worktree", NULL))
1828 warning(_("Could not unset core.worktree setting in submodule '%s'"),
1829 sub->path);
1830
1829 - free(config_path);
1831 + strbuf_release(&config_path);
1832 }
1833
1834 static const char *get_super_prefix_or_empty(void)
@@ -1922,20 +1924,22 @@ int submodule_move_head(const char *path,
1924 absorb_git_dir_into_superproject(path,
1925 ABSORB_GITDIR_RECURSE_SUBMODULES);
1926 } else {
1925 - char *gitdir = xstrfmt("%s/modules/%s",
1926 - get_git_dir(), sub->name);
1927 - connect_work_tree_and_git_dir(path, gitdir, 0);
1928 - free(gitdir);
1927 + struct strbuf gitdir = STRBUF_INIT;
1928 + submodule_name_to_gitdir(&gitdir, the_repository,
1929 + sub->name);
1930 + connect_work_tree_and_git_dir(path, gitdir.buf, 0);
1931 + strbuf_release(&gitdir);
1932
1933 /* make sure the index is clean as well */
1934 submodule_reset_index(path);
1935 }
1936
1937 if (old_head && (flags & SUBMODULE_MOVE_HEAD_FORCE)) {
1935 - char *gitdir = xstrfmt("%s/modules/%s",
1936 - get_git_dir(), sub->name);
1937 - connect_work_tree_and_git_dir(path, gitdir, 1);
1938 - free(gitdir);
1938 + struct strbuf gitdir = STRBUF_INIT;
1939 + submodule_name_to_gitdir(&gitdir, the_repository,
1940 + sub->name);
1941 + connect_work_tree_and_git_dir(path, gitdir.buf, 1);
1942 + strbuf_release(&gitdir);
1943 }
1944 }
1945
@@ -2050,7 +2054,7 @@ int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
2054 static void relocate_single_git_dir_into_superproject(const char *path)
2055 {
2056 char *old_git_dir = NULL, *real_old_git_dir = NULL, *real_new_git_dir = NULL;
2053 - char *new_git_dir;
2057 + struct strbuf new_gitdir = STRBUF_INIT;
2058 const struct submodule *sub;
2059
2060 if (submodule_uses_worktrees(path))
@@ -2068,14 +2072,13 @@ static void relocate_single_git_dir_into_superproject(const char *path)
2072 if (!sub)
2073 die(_("could not lookup name for submodule '%s'"), path);
2074
2071 - new_git_dir = git_pathdup("modules/%s", sub->name);
2072 - if (validate_submodule_git_dir(new_git_dir, sub->name) < 0)
2075 + submodule_name_to_gitdir(&new_gitdir, the_repository, sub->name);
2076 + if (validate_submodule_git_dir(new_gitdir.buf, sub->name) < 0)
2077 die(_("refusing to move '%s' into an existing git dir"),
2078 real_old_git_dir);
2075 - if (safe_create_leading_directories_const(new_git_dir) < 0)
2076 - die(_("could not create directory '%s'"), new_git_dir);
2077 - real_new_git_dir = real_pathdup(new_git_dir, 1);
2078 - free(new_git_dir);
2079 + if (safe_create_leading_directories_const(new_gitdir.buf) < 0)
2080 + die(_("could not create directory '%s'"), new_gitdir.buf);
2081 + real_new_git_dir = real_pathdup(new_gitdir.buf, 1);
2082
2083 fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"),
2084 get_super_prefix_or_empty(), path,
@@ -2086,6 +2089,7 @@ static void relocate_single_git_dir_into_superproject(const char *path)
2089 free(old_git_dir);
2090 free(real_old_git_dir);
2091 free(real_new_git_dir);
2092 + strbuf_release(&new_gitdir);
2093 }
2094
2095 /*
@@ -2105,6 +2109,7 @@ void absorb_git_dir_into_superproject(const char *path,
2109 /* Not populated? */
2110 if (!sub_git_dir) {
2111 const struct submodule *sub;
2112 + struct strbuf sub_gitdir = STRBUF_INIT;
2113
2114 if (err_code == READ_GITFILE_ERR_STAT_FAILED) {
2115 /* unpopulated as expected */
@@ -2126,8 +2131,9 @@ void absorb_git_dir_into_superproject(const char *path,
2131 sub = submodule_from_path(the_repository, null_oid(), path);
2132 if (!sub)
2133 die(_("could not lookup name for submodule '%s'"), path);
2129 - connect_work_tree_and_git_dir(path,
2130 - git_path("modules/%s", sub->name), 0);
2134 + submodule_name_to_gitdir(&sub_gitdir, the_repository, sub->name);
2135 + connect_work_tree_and_git_dir(path, sub_gitdir.buf, 0);
2136 + strbuf_release(&sub_gitdir);
2137 } else {
2138 /* Is it already absorbed into the superprojects git dir? */
2139 char *real_sub_git_dir = real_pathdup(sub_git_dir, 1);
@@ -2278,9 +2284,36 @@ int submodule_to_gitdir(struct strbuf *buf, const char *submodule)
2284 goto cleanup;
2285 }
2286 strbuf_reset(buf);
2281 - strbuf_git_path(buf, "%s/%s", "modules", sub->name);
2287 + submodule_name_to_gitdir(buf, the_repository, sub->name);
2288 }
2289
2290 cleanup:
2291 return ret;
2292 }
2293 +
2294 +void submodule_name_to_gitdir(struct strbuf *buf, struct repository *r,
2295 + const char *submodule_name)
2296 +{
2297 + /*
2298 + * NEEDSWORK: The current way of mapping a submodule's name to
2299 + * its location in .git/modules/ has problems with some naming
2300 + * schemes. For example, if a submodule is named "foo" and
2301 + * another is named "foo/bar" (whether present in the same
2302 + * superproject commit or not - the problem will arise if both
2303 + * superproject commits have been checked out at any point in
2304 + * time), or if two submodule names only have different cases in
2305 + * a case-insensitive filesystem.
2306 + *
2307 + * There are several solutions, including encoding the path in
2308 + * some way, introducing a submodule.<name>.gitdir config in
2309 + * .git/config (not .gitmodules) that allows overriding what the
2310 + * gitdir of a submodule would be (and teach Git, upon noticing
2311 + * a clash, to automatically determine a non-clashing name and
2312 + * to write such a config), or introducing a
2313 + * submodule.<name>.gitdir config in .gitmodules that repo
2314 + * administrators can explicitly set. Nothing has been decided,
2315 + * so for now, just append the name at the end of the path.
2316 + */
2317 + strbuf_repo_git_path(buf, r, "modules/");
2318 + strbuf_addstr(buf, submodule_name);
2319 +}
submodule.h
+7
@@ -124,6 +124,13 @@ int push_unpushed_submodules(struct repository *r,
124 */
125 int submodule_to_gitdir(struct strbuf *buf, const char *submodule);
126
127 +/*
128 + * Given a submodule name, create a path to where the submodule's gitdir lives
129 + * inside of the provided repository's 'modules' directory.
130 + */
131 +void submodule_name_to_gitdir(struct strbuf *buf, struct repository *r,
132 + const char *submodule_name);
133 +
134 /*
135 * Make sure that no submodule's git dir is nested in a sibling submodule's.
136 */