Skip to content

Commit

Permalink
Correctly parse Git submodule URLs
Browse files Browse the repository at this point in the history
  • Loading branch information
evanrittenhouse committed Nov 12, 2022
1 parent 28b0b1d commit dc15d3c
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 5 deletions.
30 changes: 25 additions & 5 deletions src/poetry/vcs/git/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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,
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions tests/integration/test_utils_vcs_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit dc15d3c

Please sign in to comment.