diff --git a/src/poetry/vcs/git/backend.py b/src/poetry/vcs/git/backend.py index 7b37d259690..a226766c0ef 100644 --- a/src/poetry/vcs/git/backend.py +++ b/src/poetry/vcs/git/backend.py @@ -321,23 +321,43 @@ def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo: return local @classmethod - def _clone_submodules(cls, repo: Repo) -> None: + def _clone_submodules(cls, repo: Repo, main_url: str) -> None: """ Helper method to identify configured submodules and clone them recursively. """ repo_root = Path(repo.path) modules_config = repo_root.joinpath(".gitmodules") + # A relative URL by definition starts with ../ or ./ + relative_submodule_regex = re.compile("^(\\.\\./|\\./),?") + if modules_config.exists(): config = ConfigFile.from_path(str(modules_config)) url: bytes path: bytes submodules = parse_submodules(config) + for path, url, name in submodules: path_relative = Path(path.decode("utf-8")) path_absolute = repo_root.joinpath(path_relative) + url_string = url.decode() + final_url = url_string + submodule_is_relative = bool( + relative_submodule_regex.search(url_string) + ) + if submodule_is_relative: + # Find a root list of url sections to mutate according + # to the relative url + url_sections = [section + "/" for section in main_url.split("/")] + directories_upward = url_string.count("../") + # Walk up the main URL until we know where to insert + # the submodule URL + url_portion_remaining = "".join(url_sections[:-directories_upward]) + # Insert the submodule URL + final_url = url_portion_remaining + url_string.split("../")[1] + source_root = path_absolute.parent source_root.mkdir(parents=True, exist_ok=True) @@ -354,7 +374,7 @@ def _clone_submodules(cls, repo: Repo) -> None: continue cls.clone( - url=url.decode("utf-8"), + url=final_url, source_root=source_root, name=path_relative.name, revision=revision, @@ -366,8 +386,8 @@ def _clone_submodules(cls, repo: Repo) -> None: def is_using_legacy_client() -> bool: from poetry.config.config import Config - legacy_client: bool = Config.create().get( - "experimental.system-git-client", False + legacy_client: bool = ( + Config.create().get("experimental", {}).get("system-git-client", False) ) return legacy_client @@ -424,7 +444,7 @@ def clone( try: if not cls.is_using_legacy_client(): local = cls._clone(url=url, refspec=refspec, target=target) - cls._clone_submodules(repo=local) + cls._clone_submodules(repo=local, main_url=url) return local except HTTPUnauthorized: # we do this here to handle http authenticated repositories as dulwich diff --git a/tests/integration/test_utils_vcs_git.py b/tests/integration/test_utils_vcs_git.py index 1ca7ea0c843..7c9028bff8b 100644 --- a/tests/integration/test_utils_vcs_git.py +++ b/tests/integration/test_utils_vcs_git.py @@ -240,6 +240,17 @@ def test_git_clone_clones_submodules(source_url: str) -> None: assert len(list(submodule_package_directory.glob("*"))) > 1 +def test_git_clone_clones_submodules1_with_relative_urls(source_url: str) -> None: + with Git.clone(url=source_url, branch="relative_submodule") as repo: + submodule_package_directory = ( + Path(repo.path) / "submodules" / "relative-url-submodule" + ) + + assert submodule_package_directory.exists() + assert submodule_package_directory.joinpath("README.md").exists() + assert len(list(submodule_package_directory.glob("*"))) > 1 + + def test_system_git_fallback_on_http_401( mocker: MockerFixture, source_url: str,