submodule: error out if gitdir name is too long

Encoding submodule names increases their name size, so there is an
increased risk to hit the max filename length in the gitdir path.
(the likelihood is still rather small, so it's an acceptable risk)

This gitdir file-name-too-long corner case can be be addressed in
multiple ways, including sharding or trimming, however for now, just
add the portable logic (suggested by Peff) to detect the corner case
then error out to avoid committing to a specific policy (or policies).

In the future, instead of throwing an error (which we do now anyway
without submodule encoding), we could maybe let the user specify via
configs how to address this case, e.g. pick trimming or sharding.

At least now we print a nice error instead of the OS defaults which
can be rather cryptic for users.

Suggested-by: Jeff King <peff@peff.net>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
seen
Adrian Ratiu 2025-10-06 14:25:18 +03:00 committed by Junio C Hamano
parent 35dbc30c2a
commit 1c1c4163db
7 changed files with 58 additions and 0 deletions

View File

@ -2210,6 +2210,11 @@ ifndef HAVE_PLATFORM_PROCINFO
COMPAT_OBJS += compat/stub/procinfo.o COMPAT_OBJS += compat/stub/procinfo.o
endif endif


ifdef NO_PATHCONF
COMPAT_CFLAGS += -DNO_PATHCONF
COMPAT_OBJS += compat/pathconf.o
endif

ifdef RUNTIME_PREFIX ifdef RUNTIME_PREFIX


ifdef HAVE_BSD_KERN_PROC_SYSCTL ifdef HAVE_BSD_KERN_PROC_SYSCTL

10
compat/pathconf.c Normal file
View File

@ -0,0 +1,10 @@
#include "git-compat-util.h"

/*
* Minimal stub for platforms without pathconf() (e.g. Windows),
* to fall back to NAME_MAX from limits.h or compat/posix.h.
*/
long git_pathconf(const char *path UNUSED, int name UNUSED)
{
return -1;
}

View File

@ -250,6 +250,14 @@ char *gitdirname(char *);
#define NAME_MAX 255 #define NAME_MAX 255
#endif #endif


#ifdef NO_PATHCONF
#ifndef _PC_NAME_MAX
#define _PC_NAME_MAX 1 /* dummy value, only used for git_pathconf */
#endif
#define pathconf(a,b) git_pathconf(a,b)
long git_pathconf(const char *path, int name);
#endif

typedef uintmax_t timestamp_t; typedef uintmax_t timestamp_t;
#define PRItime PRIuMAX #define PRItime PRIuMAX
#define parse_timestamp strtoumax #define parse_timestamp strtoumax

View File

@ -473,6 +473,7 @@ ifeq ($(uname_S),Windows)
NEEDS_CRYPTO_WITH_SSL = YesPlease NEEDS_CRYPTO_WITH_SSL = YesPlease
NO_LIBGEN_H = YesPlease NO_LIBGEN_H = YesPlease
NO_POLL = YesPlease NO_POLL = YesPlease
NO_PATHCONF = YesPlease
NO_SYMLINK_HEAD = YesPlease NO_SYMLINK_HEAD = YesPlease
NO_IPV6 = YesPlease NO_IPV6 = YesPlease
NO_SETENV = YesPlease NO_SETENV = YesPlease
@ -688,6 +689,7 @@ ifeq ($(uname_S),MINGW)
NEEDS_CRYPTO_WITH_SSL = YesPlease NEEDS_CRYPTO_WITH_SSL = YesPlease
NO_LIBGEN_H = YesPlease NO_LIBGEN_H = YesPlease
NO_POLL = YesPlease NO_POLL = YesPlease
NO_PATHCONF = YesPlease
NO_SYMLINK_HEAD = YesPlease NO_SYMLINK_HEAD = YesPlease
NO_SETENV = YesPlease NO_SETENV = YesPlease
NO_STRCASESTR = YesPlease NO_STRCASESTR = YesPlease

View File

@ -1392,6 +1392,7 @@ checkfuncs = {
'initgroups' : [], 'initgroups' : [],
'strtoumax' : ['strtoumax.c', 'strtoimax.c'], 'strtoumax' : ['strtoumax.c', 'strtoimax.c'],
'pread' : ['pread.c'], 'pread' : ['pread.c'],
'pathconf' : ['pathconf.c'],
} }


if host_machine.system() == 'windows' if host_machine.system() == 'windows'

View File

@ -2625,13 +2625,29 @@ void submodule_name_to_gitdir(struct strbuf *buf, struct repository *r,


if (the_repository->repository_format_submodule_encoding) { if (the_repository->repository_format_submodule_encoding) {
struct strbuf tmp = STRBUF_INIT; struct strbuf tmp = STRBUF_INIT;
size_t base_len;
long name_max;


strbuf_reset(buf); strbuf_reset(buf);
repo_git_path_append(r, buf, "modules/"); repo_git_path_append(r, buf, "modules/");
base_len = buf->len;


strbuf_addstr_urlencode(&tmp, submodule_name, is_rfc3986_unreserved); strbuf_addstr_urlencode(&tmp, submodule_name, is_rfc3986_unreserved);
strbuf_addstr_case_encode(buf, tmp.buf); strbuf_addstr_case_encode(buf, tmp.buf);


/* Ensure final path length is below NAME_MAX after encoding */
name_max = pathconf(buf->buf, _PC_NAME_MAX);
if (name_max == -1)
name_max = NAME_MAX;

if (buf->len - base_len > name_max)
/*
* TODO: make this smarter; instead of erroring out, maybe we could trim or
* shard the gitdir names to make them fit under NAME_MAX.
*/
die(_("submodule name %s is too long (%"PRIuMAX" bytes, limit %"PRIuMAX")"),
buf->buf, (uintmax_t)buf->len - base_len, (uintmax_t)name_max);

strbuf_release(&tmp); strbuf_release(&tmp);
} }
} }

View File

@ -143,4 +143,20 @@ test_expect_success 'submodule git dir nesting detection must work with parallel
verify_submodule_gitdir_path clone_parallel hippo/hooks modules/hippo%2fhooks verify_submodule_gitdir_path clone_parallel hippo/hooks modules/hippo%2fhooks
' '


test_expect_success 'submodule encoded name exceeds max name limit' '
(
cd main &&

# find the system NAME_MAX (fall back to 255 if unknown)
name_max=$(getconf NAME_MAX . 2>/dev/null || echo 255) &&

# each "%" char encodes to "%25" (3 chars), ensure we exceed NAME_MAX
count=$((name_max + 10)) &&
longname=$(test_seq -f "%%%0.s" 1 $count | tr -d "\n") &&

test_must_fail git submodule add ../new-sub "$longname" 2>err &&
test_grep "fatal: submodule name .* is too long" err
)
'

test_done test_done