Skip to content

Commit dec8068

Browse files
committed
Merge branch 'ar/submodule-gitdir-tweak' into seen
Avoid local submodule repository directory paths overlapping with each other by encoding submodule names before using them as path components. * ar/submodule-gitdir-tweak: meson/Makefile: allow setting submodule encoding at build time submodule: use hashed name for gitdir submodule: fix case-folding gitdir filesystem colisions submodule: add extension to encode gitdir paths submodule: always validate gitdirs inside submodule_name_to_gitdir builtin/credential-store: move is_rfc3986_unreserved to url.[ch] submodule--helper: use submodule_name_to_gitdir in add_submodule
2 parents 2e324a8 + 7075000 commit dec8068

20 files changed

+591
-81
lines changed

Documentation/config/extensions.adoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ relativeWorktrees:::
7373
repaired with either the `--relative-paths` option or with the
7474
`worktree.useRelativePaths` config set to `true`.
7575
76+
submoduleEncoding:::
77+
If enabled, submodule gitdir paths are encoded to avoid filesystem
78+
conflicts due to nested gitdirs, case insensitivity or other issues.
79+
When enabled, the submodule.<name>.gitdir config is always set for
80+
all submodules and is the single point of authority for gitdir paths.
81+
Can also be enabled via the submodule-encoding build option. The repo
82+
config takes precedence over the build-time default.
83+
7684
worktreeConfig:::
7785
If enabled, then worktrees will load config settings from the
7886
`$GIT_DIR/config.worktree` file in addition to the

Documentation/config/submodule.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ submodule.<name>.active::
5252
submodule.active config option. See linkgit:gitsubmodules[7] for
5353
details.
5454

55+
submodule.<name>.gitdir::
56+
This option sets the gitdir path for submodule <name>, allowing users to
57+
override the default path. Only works when `extensions.submoduleEncoding`
58+
is enabled, otherwise does nothing. See linkgit:git-config[1] for details.
59+
5560
submodule.active::
5661
A repeated field which contains a pathspec used to match against a
5762
submodule's path to determine if the submodule is of interest to git

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2348,6 +2348,11 @@ ifdef INCLUDE_LIBGIT_RS
23482348
BASIC_CFLAGS += -fvisibility=hidden
23492349
endif
23502350

2351+
ifdef SUBMODULE_ENCODING_BY_DEFAULT
2352+
# Set submoduleEncoding extension default specified at build time
2353+
BASIC_CFLAGS += -DSUBMODULE_ENCODING_BY_DEFAULT=$(SUBMODULE_ENCODING_BY_DEFAULT)
2354+
endif
2355+
23512356
ifeq ($(TCLTK_PATH),)
23522357
NO_TCLTK = NoThanks
23532358
endif

builtin/credential-store.c

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "path.h"
88
#include "string-list.h"
99
#include "parse-options.h"
10+
#include "url.h"
1011
#include "write-or-die.h"
1112

1213
static struct lock_file credential_lock;
@@ -76,12 +77,6 @@ static void rewrite_credential_file(const char *fn, struct credential *c,
7677
die_errno("unable to write credential store");
7778
}
7879

79-
static int is_rfc3986_unreserved(char ch)
80-
{
81-
return isalnum(ch) ||
82-
ch == '-' || ch == '_' || ch == '.' || ch == '~';
83-
}
84-
8580
static int is_rfc3986_reserved_or_unreserved(char ch)
8681
{
8782
if (is_rfc3986_unreserved(ch))

builtin/submodule--helper.c

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,22 @@ static int module_summary(int argc, const char **argv, const char *prefix,
12041204
return ret;
12051205
}
12061206

1207+
static int module_gitdir(int argc, const char **argv, const char *prefix UNUSED,
1208+
struct repository *repo)
1209+
{
1210+
struct strbuf gitdir = STRBUF_INIT;
1211+
1212+
if (argc != 2)
1213+
usage(_("git submodule--helper gitdir <name>"));
1214+
1215+
submodule_name_to_gitdir(&gitdir, repo, argv[1]);
1216+
1217+
printf("%s\n", gitdir.buf);
1218+
1219+
strbuf_release(&gitdir);
1220+
return 0;
1221+
}
1222+
12071223
struct sync_cb {
12081224
const char *prefix;
12091225
const char *super_prefix;
@@ -1699,10 +1715,6 @@ static int clone_submodule(const struct module_clone_data *clone_data,
16991715
clone_data_path = to_free = xstrfmt("%s/%s", repo_get_work_tree(the_repository),
17001716
clone_data->path);
17011717

1702-
if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0)
1703-
die(_("refusing to create/use '%s' in another submodule's "
1704-
"git dir"), sm_gitdir);
1705-
17061718
if (!file_exists(sm_gitdir)) {
17071719
if (clone_data->require_init && !stat(clone_data_path, &st) &&
17081720
!is_empty_dir(clone_data_path))
@@ -1776,23 +1788,6 @@ static int clone_submodule(const struct module_clone_data *clone_data,
17761788
free(path);
17771789
}
17781790

1779-
/*
1780-
* We already performed this check at the beginning of this function,
1781-
* before cloning the objects. This tries to detect racy behavior e.g.
1782-
* in parallel clones, where another process could easily have made the
1783-
* gitdir nested _after_ it was created.
1784-
*
1785-
* To prevent further harm coming from this unintentionally-nested
1786-
* gitdir, let's disable it by deleting the `HEAD` file.
1787-
*/
1788-
if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0) {
1789-
char *head = xstrfmt("%s/HEAD", sm_gitdir);
1790-
unlink(head);
1791-
free(head);
1792-
die(_("refusing to create/use '%s' in another submodule's "
1793-
"git dir"), sm_gitdir);
1794-
}
1795-
17961791
connect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0);
17971792

17981793
p = repo_submodule_path(the_repository, clone_data_path, "config");
@@ -3190,13 +3185,13 @@ static void append_fetch_remotes(struct strbuf *msg, const char *git_dir_path)
31903185

31913186
static int add_submodule(const struct add_data *add_data)
31923187
{
3193-
char *submod_gitdir_path;
31943188
struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT;
31953189
struct string_list reference = STRING_LIST_INIT_NODUP;
31963190
int ret = -1;
31973191

31983192
/* perhaps the path already exists and is already a git repo, else clone it */
31993193
if (is_directory(add_data->sm_path)) {
3194+
char *submod_gitdir_path;
32003195
struct strbuf sm_path = STRBUF_INIT;
32013196
strbuf_addstr(&sm_path, add_data->sm_path);
32023197
submod_gitdir_path = xstrfmt("%s/.git", add_data->sm_path);
@@ -3210,10 +3205,11 @@ static int add_submodule(const struct add_data *add_data)
32103205
free(submod_gitdir_path);
32113206
} else {
32123207
struct child_process cp = CHILD_PROCESS_INIT;
3208+
struct strbuf submod_gitdir = STRBUF_INIT;
32133209

3214-
submod_gitdir_path = xstrfmt(".git/modules/%s", add_data->sm_name);
3210+
submodule_name_to_gitdir(&submod_gitdir, the_repository, add_data->sm_name);
32153211

3216-
if (is_directory(submod_gitdir_path)) {
3212+
if (is_directory(submod_gitdir.buf)) {
32173213
if (!add_data->force) {
32183214
struct strbuf msg = STRBUF_INIT;
32193215
char *die_msg;
@@ -3222,8 +3218,8 @@ static int add_submodule(const struct add_data *add_data)
32223218
"locally with remote(s):\n"),
32233219
add_data->sm_name);
32243220

3225-
append_fetch_remotes(&msg, submod_gitdir_path);
3226-
free(submod_gitdir_path);
3221+
append_fetch_remotes(&msg, submod_gitdir.buf);
3222+
strbuf_release(&submod_gitdir);
32273223

32283224
strbuf_addf(&msg, _("If you want to reuse this local git "
32293225
"directory instead of cloning again from\n"
@@ -3241,7 +3237,7 @@ static int add_submodule(const struct add_data *add_data)
32413237
"submodule '%s'\n"), add_data->sm_name);
32423238
}
32433239
}
3244-
free(submod_gitdir_path);
3240+
strbuf_release(&submod_gitdir);
32453241

32463242
clone_data.prefix = add_data->prefix;
32473243
clone_data.path = add_data->sm_path;
@@ -3594,6 +3590,7 @@ int cmd_submodule__helper(int argc,
35943590
NULL
35953591
};
35963592
struct option options[] = {
3593+
OPT_SUBCOMMAND("gitdir", &fn, module_gitdir),
35973594
OPT_SUBCOMMAND("clone", &fn, module_clone),
35983595
OPT_SUBCOMMAND("add", &fn, module_add),
35993596
OPT_SUBCOMMAND("update", &fn, module_update),

configure.ac

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,29 @@ AC_ARG_ENABLE([cssmin],
229229
GIT_CONF_SUBST([CSSMIN])
230230
])
231231

232+
# Define option to enable the submodule encoding extension by default
233+
AC_ARG_ENABLE([submodule-encoding],
234+
[AS_HELP_STRING([--enable-submodule-encoding],
235+
[Enable the submoduleEncoding extension by default at build time.]
236+
[--disable-submodule-encoding will keep the current default (disabled).])],
237+
[
238+
case "$enableval" in
239+
yes) SUBMODULE_ENCODING_BY_DEFAULT=1
240+
AC_MSG_NOTICE([Submodule encoding will be enabled by default.])
241+
;;
242+
no) SUBMODULE_ENCODING_BY_DEFAULT=0
243+
AC_MSG_NOTICE([Submodule encoding will not be enabled by default.])
244+
;;
245+
*) AC_MSG_ERROR([--enable-submodule-encoding takes yes or no.])
246+
;;
247+
esac
248+
],
249+
[
250+
SUBMODULE_ENCODING_BY_DEFAULT=0
251+
])
252+
253+
GIT_CONF_SUBST([SUBMODULE_ENCODING_BY_DEFAULT])
254+
232255
## Site configuration (override autodetection)
233256
## --with-PACKAGE[=ARG] and --without-PACKAGE
234257
AC_MSG_NOTICE([CHECKS for site configuration])

meson.build

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,10 @@ else
947947
build_options_config.set('NO_PERL_CPAN_FALLBACKS', '')
948948
endif
949949

950+
submodule_encoding_by_default = get_option('submodule-encoding').to_int()
951+
libgit_c_args += '-DSUBMODULE_ENCODING_BY_DEFAULT=' + submodule_encoding_by_default.to_string()
952+
build_options_config.set('SUBMODULE_ENCODING_BY_DEFAULT', submodule_encoding_by_default)
953+
950954
zlib_backend = get_option('zlib_backend')
951955
if zlib_backend in ['auto', 'zlib-ng']
952956
zlib_ng = dependency('zlib-ng', required: zlib_backend == 'zlib-ng')

meson_options.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ option('runtime_prefix', type: 'boolean', value: false,
2121
description: 'Resolve ancillary tooling and support files relative to the location of the runtime binary instead of hard-coding them into the binary.')
2222
option('sane_tool_path', type: 'array', value: [],
2323
description: 'An array of paths to pick up tools from in case the normal tools are broken or lacking.')
24+
option('submodule-encoding', type: 'boolean', value: false,
25+
description: 'Enable submoduleEncoding extension by default at build time.')
2426

2527
# Build information compiled into Git and other parts like documentation.
2628
option('build_date', type: 'string', value: '',

repository.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ int repo_init(struct repository *repo,
292292
repo->repository_format_worktree_config = format.worktree_config;
293293
repo->repository_format_relative_worktrees = format.relative_worktrees;
294294
repo->repository_format_precious_objects = format.precious_objects;
295+
repo->repository_format_submodule_encoding = format.submodule_encoding;
295296

296297
/* take ownership of format.partial_clone */
297298
repo->repository_format_partial_clone = format.partial_clone;

repository.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ struct repository {
158158
int repository_format_worktree_config;
159159
int repository_format_relative_worktrees;
160160
int repository_format_precious_objects;
161+
int repository_format_submodule_encoding;
161162

162163
/* Indicate if a repository has a different 'commondir' from 'gitdir' */
163164
unsigned different_commondir:1;

0 commit comments

Comments
 (0)