2929if sys .platform == "win32" :
3030 __all__ .append ("to_native_path_windows" )
3131
32- from abc import abstractmethod
3332import contextlib
34- from functools import wraps
3533import getpass
3634import logging
3735import ntpath
3836import os
3937import os .path as osp
40- from pathlib import Path
4138import platform
4239import re
4340import shutil
4441import stat
4542import subprocess
4643import time
47- from urllib .parse import urlsplit , urlunsplit
4844import warnings
49-
50- # NOTE: Unused imports can be improved now that CI testing has fully resumed. Some of
51- # these be used indirectly through other GitPython modules, which avoids having to write
52- # gitdb all the time in their imports. They are not in __all__, at least currently,
53- # because they could be removed or changed at any time, and so should not be considered
54- # conceptually public to code outside GitPython. Linters of course do not like it.
55- from gitdb .util import (
56- LazyMixin , # noqa: F401
57- LockedFD , # noqa: F401
58- bin_to_hex , # noqa: F401
59- file_contents_ro , # noqa: F401
60- file_contents_ro_filepath , # noqa: F401
61- hex_to_bin , # noqa: F401
62- make_sha ,
63- to_bin_sha , # noqa: F401
64- to_hex_sha , # noqa: F401
65- )
45+ from abc import abstractmethod
46+ from functools import wraps
47+ from pathlib import Path
6648
6749# typing ---------------------------------------------------------
68-
6950from typing import (
51+ IO ,
52+ TYPE_CHECKING ,
7053 Any ,
7154 AnyStr ,
7255 Callable ,
7356 Dict ,
7457 Generator ,
75- IO ,
7658 Iterator ,
7759 List ,
7860 Optional ,
7961 Pattern ,
8062 Sequence ,
8163 Tuple ,
82- TYPE_CHECKING ,
8364 Type ,
8465 TypeVar ,
8566 Union ,
8667 cast ,
8768 overload ,
8869)
70+ from urllib .parse import urlsplit , urlunsplit
71+
72+ # NOTE: Unused imports can be improved now that CI testing has fully resumed. Some of
73+ # these be used indirectly through other GitPython modules, which avoids having to write
74+ # gitdb all the time in their imports. They are not in __all__, at least currently,
75+ # because they could be removed or changed at any time, and so should not be considered
76+ # conceptually public to code outside GitPython. Linters of course do not like it.
77+ from gitdb .util import (
78+ LazyMixin , # noqa: F401
79+ LockedFD , # noqa: F401
80+ bin_to_hex , # noqa: F401
81+ file_contents_ro , # noqa: F401
82+ file_contents_ro_filepath , # noqa: F401
83+ hex_to_bin , # noqa: F401
84+ make_sha ,
85+ to_bin_sha , # noqa: F401
86+ to_hex_sha , # noqa: F401
87+ )
8988
9089if TYPE_CHECKING :
9190 from git .cmd import Git
9493 from git .repo .base import Repo
9594
9695from git .types import (
96+ HSH_TD ,
9797 Files_TD ,
9898 Has_id_attribute ,
99- HSH_TD ,
10099 Literal ,
101100 PathLike ,
102101 Protocol ,
@@ -385,12 +384,27 @@ def _to_relative_path(root: PathLike, path: PathLike) -> str:
385384 "" , "" , "\u200c \u200d \u200e \u200f \u202a \u202b \u202c \u202d \u202e \u206a \u206b \u206c \u206d \u206e \u206f \ufeff "
386385)
387386
387+ # Match Git's is_ntfs_dotgitmodules in path.c on a lowercased, trimmed name.
388+ # Besides gitmod~1..4, fallback aliases have exactly eight ASCII characters:
389+ # a shrinking prefix of "gi7eba", "~", and digits with no leading zero.
390+ # Explicit digit counts avoid accepting shorter/longer names or Unicode digits.
391+ _NTFS_DOTGITMODULES_SHORT_NAME = re .compile (
392+ r"(?:gitmod~[1-4]|gi7eba~[1-9]|gi7eb~[1-9][0-9]|gi7e~[1-9][0-9]{2}|"
393+ r"gi7~[1-9][0-9]{3}|gi~[1-9][0-9]{4}|g~[1-9][0-9]{5}|~[1-9][0-9]{6})"
394+ )
395+
388396
389- def _validate_repo_path (path : PathLike ) -> None :
397+ def _validate_repo_path (path : PathLike , mode : Union [ int , None ] = None ) -> None :
390398 """Reject unsafe tree/index paths without normalizing away their components.
391399
392400 Protect Git metadata aliases on NTFS and HFS even when writing on another
393401 platform. Other POSIX filename characters, including newlines, remain valid.
402+
403+ :param mode:
404+ Mode of the index or tree entry the path belongs to, where one is known.
405+ Git refuses a symbolic link that aliases ``.gitmodules``, since the
406+ submodule configuration would then be read through the link, so that
407+ name is only rejected once the mode says the entry is a link.
394408 """
395409 name = os .fspath (path )
396410 if not name or "\0 " in name or ntpath .splitdrive (name )[0 ] or name .startswith ("/" ):
@@ -406,6 +420,13 @@ def _validate_repo_path(path: PathLike) -> None:
406420 hfs_name = part .translate (_HFS_IGNORABLES ).lower ()
407421 if ntfs_name in (".git" , "git~1" ) or hfs_name == ".git" :
408422 raise ValueError ("Repository path aliases Git metadata: %r" % name )
423+ if mode is not None and stat .S_ISLNK (mode ):
424+ if (
425+ ntfs_name == ".gitmodules"
426+ or hfs_name == ".gitmodules"
427+ or _NTFS_DOTGITMODULES_SHORT_NAME .fullmatch (ntfs_name ) is not None
428+ ):
429+ raise ValueError ("Symbolic link aliases the submodule configuration: %r" % name )
409430
410431
411432def assure_directory_exists (path : PathLike , is_file : bool = False ) -> bool :
0 commit comments