Git dependencies' submodules with relative URLs handled incorrectly (regression from 1.1)
See original GitHub issue-
I am on the latest Poetry version.
-
I have searched the issues of this repo and believe that this is not a duplicate.
-
If an exception occurs when executing a command, I executed it again in debug mode (
-vvvoption). -
OS version and name: Ubuntu 20.04
-
Poetry version: 1.2.0
Issue
I have a project with a git dependency. The dependency uses git submodules, and the dependencyโs .gitmodules file contains a submodule using a relative URL, e.g.:
[submodule "foo/bar"]
path = foo/bar
url = ../bar.git
When Poetry attempts to install the dependency, it clones the repository and then attempts to clone each submodule, but it passes the raw relative URL from the configuration to _clone() and thus _fetch_remote_refs(), which calls Dulwichโs get_transport_and_path(), which decides the URL is for a local repository so it attempts to find ../bar.git on the filesystem, which fails. I think it should instead detect relative URLs and append them to the root repositoryโs URL so that Dulwich will realise itโs a remote repo and return the appropriate client.
If I clone the dependency using git on the command line, with --recurse-submodules, git correctly uses a relative URL to fetch the submodule. If I force Poetry to use the legacy system git instead of Dulwich, it doesnโt seem to clone the submodules at all, so also doesnโt run into trouble (in this case the dependencyโs submodule isnโt needed to install the dependency, just for testing). Similarly this also worked OK on poetry 1.1 which I think used the system git to recursively clone submodules.
It looks like this is from #5428.
Hereโs a representative traceback:
Traceback
$ poetry lock -vvv Using virtualenv: /home/x/.cache/pypoetry/virtualenvs/baz-Nz-qS6ut-py3.8 Updating dependencies Resolving dependencies... 1: fact: baz is 0.1.0 1: derived: baz Cloning ssh://git@github.com/x/foo.git at 'master' to /home/x/.cache/pypoetry/virtualenvs/baz-Nz-qS6ut-py3.8/src/foo 1: Version solving took 1.496 seconds. 1: Tried 1 solutions.Stack trace:
28 ~/.local/lib/python3.8/site-packages/cleo/application.py:329 in run 327โ 328โ try: โ 329โ exit_code = self._run(io) 330โ except Exception as e: 331โ if not self._catch_exceptions:
27 ~/.local/lib/python3.8/site-packages/poetry/console/application.py:185 in _run 183โ self._load_plugins(io) 184โ โ 185โ exit_code: int = super()._run(io) 186โ return exit_code 187โ
26 ~/.local/lib/python3.8/site-packages/poethepoet/plugin.py:249 in _run 247โ tokens.insert(task_name_index, โโโ) 248โ โ 249โ continue_run(self, io) 250โ 251โ # Apply the patch
25 ~/.local/lib/python3.8/site-packages/cleo/application.py:423 in _run 421โ io.input.set_stream(stream) 422โ โ 423โ exit_code = self._run_command(command, io) 424โ self._running_command = None 425โ
24 ~/.local/lib/python3.8/site-packages/cleo/application.py:465 in _run_command 463โ 464โ if error is not None: โ 465โ raise error 466โ 467โ return event.exit_code
23 ~/.local/lib/python3.8/site-packages/cleo/application.py:449 in _run_command 447โ 448โ if event.command_should_run(): โ 449โ exit_code = command.run(io) 450โ else: 451โ exit_code = ConsoleCommandEvent.RETURN_CODE_DISABLED
22 ~/.local/lib/python3.8/site-packages/cleo/commands/base_command.py:119 in run 117โ io.input.validate() 118โ โ 119โ status_code = self.execute(io) 120โ 121โ if status_code is None:
21 ~/.local/lib/python3.8/site-packages/cleo/commands/command.py:83 in execute 81โ 82โ try: โ 83โ return self.handle() 84โ except KeyboardInterrupt: 85โ return 1
20 ~/.local/lib/python3.8/site-packages/poetry/console/commands/lock.py:54 in handle 52โ self.installer.lock(update=not self.option(โno-updateโ)) 53โ โ 54โ return self.installer.run() 55โ
19 ~/.local/lib/python3.8/site-packages/poetry/installation/installer.py:111 in run 109โ self._execute_operations = False 110โ โ 111โ return self._do_install() 112โ 113โ def dry_run(self, dry_run: bool = True) -> Installer:
18 ~/.local/lib/python3.8/site-packages/poetry/installation/installer.py:244 in _do_install 242โ source_root=self._env.path.joinpath(โsrcโ) 243โ ): โ 244โ ops = solver.solve(use_latest=self._whitelist).calculate_operations() 245โ else: 246โ self._io.write_line(โInstalling dependencies from lock fileโ)
17 ~/.local/lib/python3.8/site-packages/poetry/puzzle/solver.py:73 in solve 71โ with self._provider.progress(): 72โ start = time.time() โ 73โ packages, depths = self._solve(use_latest=use_latest) 74โ end = time.time() 75โ
16 ~/.local/lib/python3.8/site-packages/poetry/puzzle/solver.py:151 in _solve 149โ 150โ try: โ 151โ result = resolve_version( 152โ self._package, self._provider, locked=locked, use_latest=use_latest 153โ )
15 ~/.local/lib/python3.8/site-packages/poetry/mixology/init.py:24 in resolve_version 22โ solver = VersionSolver(root, provider, locked=locked, use_latest=use_latest) 23โ โ 24โ return solver.solve() 25โ
14 ~/.local/lib/python3.8/site-packages/poetry/mixology/version_solver.py:127 in solve 125โ while next is not None: 126โ self._propagate(next) โ 127โ next = self._choose_package_version() 128โ 129โ return self._result()
13 ~/.local/lib/python3.8/site-packages/poetry/mixology/version_solver.py:446 in _choose_package_version 444โ package = locked 445โ โ 446โ package = self._provider.complete_package(package) 447โ 448โ conflict = False
12 ~/.local/lib/python3.8/site-packages/poetry/puzzle/provider.py:556 in complete_package 554โ for r in requires: 555โ if r.is_direct_origin(): โ 556โ self.search_for_direct_origin_dependencyยฎ 557โ 558โ optional_dependencies = []
11 ~/.local/lib/python3.8/site-packages/poetry/puzzle/provider.py:230 in search_for_direct_origin_dependency 228โ elif dependency.is_vcs(): 229โ dependency = cast(โVCSDependencyโ, dependency) โ 230โ package = self._search_for_vcs(dependency) 231โ 232โ elif dependency.is_file():
10 ~/.local/lib/python3.8/site-packages/poetry/puzzle/provider.py:312 in _search_for_vcs 310โ and get the information we need by checking out the specified reference. 311โ โโ" โ 312โ package = self.get_package_from_vcs( 313โ dependency.vcs, 314โ dependency.source,
9 ~/.local/lib/python3.8/site-packages/poetry/puzzle/provider.py:342 in get_package_from_vcs 340โ raise ValueError(f"Unsupported VCS dependency {vcs}") 341โ โ 342โ return _get_package_from_git( 343โ url=url, 344โ branch=branch,
8 ~/.local/lib/python3.8/site-packages/poetry/puzzle/provider.py:96 in _get_package_from_git 94โ source_root: Path | None = None, 95โ ) -> Package: โ 96โ source = Git.clone( 97โ url=url, 98โ source_root=source_root,
7 ~/.local/lib/python3.8/site-packages/poetry/vcs/git/backend.py:427 in clone 425โ if not cls.is_using_legacy_client(): 426โ local = cls._clone(url=url, refspec=refspec, target=target) โ 427โ cls._clone_submodules(repo=local) 428โ return local 429โ except HTTPUnauthorized:
6 ~/.local/lib/python3.8/site-packages/poetry/vcs/git/backend.py:356 in _clone_submodules 354โ continue 355โ โ 356โ cls.clone( 357โ url=url.decode(โutf-8โ), 358โ source_root=source_root,
5 ~/.local/lib/python3.8/site-packages/poetry/vcs/git/backend.py:426 in clone 424โ try: 425โ if not cls.is_using_legacy_client(): โ 426โ local = cls._clone(url=url, refspec=refspec, target=target) 427โ cls._clone_submodules(repo=local) 428โ return local
4 ~/.local/lib/python3.8/site-packages/poetry/vcs/git/backend.py:258 in _clone 256โ local = Repo(str(target)) 257โ โ 258โ remote_refs = cls._fetch_remote_refs(url=url, local=local) 259โ 260โ logger.debug(
3 ~/.local/lib/python3.8/site-packages/poetry/vcs/git/backend.py:201 in _fetch_remote_refs 199โ 200โ with local: โ 201โ result: FetchPackResult = client.fetch( 202โ path, 203โ local,
2 ~/.local/lib/python3.8/site-packages/dulwich/client.py:1501 in fetch 1499โ 1500โ โโ" โ 1501โ with self._open_repo(path) as r: 1502โ refs = r.fetch( 1503โ target,
1 ~/.local/lib/python3.8/site-packages/dulwich/client.py:1423 in _open_repo 1421โ if not isinstance(path, str): 1422โ path = os.fsdecode(path) โ 1423โ return closing(Repo(path)) 1424โ 1425โ def send_pack(self, path, update_refs, generate_pack_data, progress=None):
NotGitRepository
No git repository was found at โฆ/bar.git
at ~/.local/lib/python3.8/site-packages/dulwich/repo.py:1090 in init 1086โ elif (os.path.isdir(os.path.join(root, OBJECTDIR)) 1087โ and os.path.isdir(os.path.join(root, REFSDIR))): 1088โ bare = True 1089โ else: โ 1090โ raise NotGitRepository( 1091โ โNo git repository was found at %(path)sโ % dict(path=root) 1092โ ) 1093โ 1094โ self.bare = bare
Issue Analytics
- State:
- Created a year ago
- Reactions:1
- Comments:14 (13 by maintainers)

Top Related StackOverflow Question
The issue on the Dulwich side is for porcelain; poetry uses the plumbing from Dulwich directly. Thereโs already a function in the plumbing for getting o list of submodules.
Apologies for the delay, work has been crazy. Commenting here so the issue doesnโt get picked up by someone else as Iโm basically code complete - will hopefully be able to submit a PR next weekend.