def test_git_clone_fails_for_non_existent_revision(source_url: str): revision = sha1(uuid.uuid4().bytes).hexdigest() with pytest.raises(PoetrySimpleConsoleException) as e: Git.clone(url=source_url, revision=revision) assert f"Failed to clone {source_url} at '{revision}'" in str(e.value)
def get_package_from_vcs( cls, vcs, url, reference=None, name=None ): # type: (str, str, Optional[str], Optional[str]) -> Package if vcs != "git": raise ValueError("Unsupported VCS dependency {}".format(vcs)) tmp_dir = Path( mkdtemp(prefix="pypoetry-git-{}".format( url.split("/")[-1].rstrip(".git")))) try: git = Git() git.clone(url, tmp_dir) if reference is not None: git.checkout(reference, tmp_dir) else: reference = "HEAD" revision = git.rev_parse(reference, tmp_dir).strip() package = cls.get_package_from_directory(tmp_dir, name=name) package.source_type = "git" package.source_url = url package.source_reference = revision except Exception: raise finally: safe_rmtree(str(tmp_dir)) return package
def test_git_clone_fails_for_non_existent_branch(source_url: str): branch = uuid.uuid4().hex with pytest.raises(PoetrySimpleConsoleException) as e: Git.clone(url=source_url, branch=branch) assert f"Failed to clone {source_url} at '{branch}'" in str(e.value)
def _get_package_from_git( url: str, branch: str | None = None, tag: str | None = None, rev: str | None = None, subdirectory: str | None = None, source_root: Path | None = None, ) -> Package: source = Git.clone( url=url, source_root=source_root, branch=branch, tag=tag, revision=rev, clean=False, ) revision = Git.get_revision(source) path = Path(source.path) if subdirectory: path = path.joinpath(subdirectory) package = Provider.get_package_from_directory(path) package._source_type = "git" package._source_url = url package._source_reference = rev or tag or branch or "HEAD" package._source_resolved_reference = revision package._source_subdirectory = subdirectory return package
def test_git_local_info(source_url: str, remote_refs: FetchPackResult, remote_default_ref: bytes) -> None: with Git.clone(url=source_url) as repo: info = Git.info(repo=repo) assert info.origin == source_url assert info.revision == remote_refs.refs[remote_default_ref].decode( "utf-8")
def _install_git(self, operation: Install | Update) -> int: from poetry.vcs.git import Git package = operation.package operation_message = self.get_operation_message(operation) message = ( f" <fg=blue;options=bold>•</> {operation_message}: <info>Cloning...</info>" ) self._write(operation, message) source = Git.clone( url=package.source_url, source_root=self._env.path / "src", revision=package.source_resolved_reference or package.source_reference, ) # Now we just need to install from the source directory original_url = package.source_url package._source_url = str(source.path) status_code = self._install_directory(operation) package._source_url = original_url return status_code
def test_git_clone_clones_submodules(source_url: str) -> None: with Git.clone(url=source_url) as repo: submodule_package_directory = (Path(repo.path) / "submodules" / "sample-namespace-packages") assert submodule_package_directory.exists() assert submodule_package_directory.joinpath("README.md").exists() assert len(list(submodule_package_directory.glob("*"))) > 1
def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] """ Search for the specifications that match the given VCS dependency. Basically, we clone the repository in a temporary directory and get the information we need by checking out the specified reference. """ if dependency.vcs != "git": raise ValueError("Unsupported VCS dependency {}".format( dependency.vcs)) tmp_dir = Path( mkdtemp(prefix="pypoetry-git-{}".format(dependency.name))) try: git = Git() git.clone(dependency.source, tmp_dir) git.checkout(dependency.reference, tmp_dir) revision = git.rev_parse(dependency.reference, tmp_dir).strip() if dependency.tag or dependency.rev: revision = dependency.reference directory_dependency = DirectoryDependency( dependency.name, tmp_dir, category=dependency.category, optional=dependency.is_optional(), ) for extra in dependency.extras: directory_dependency.extras.append(extra) package = self.search_for_directory(directory_dependency)[0] package.source_type = "git" package.source_url = dependency.source package.source_reference = revision except Exception: raise finally: safe_rmtree(str(tmp_dir)) return [package]
def test_username_password_parameter_is_not_passed_to_dulwich( mocker: MockerFixture, source_url: str, config: Config) -> None: from poetry.vcs.git import backend spy_clone = mocker.spy(Git, "_clone") spy_get_transport_and_path = mocker.spy(backend, "get_transport_and_path") with Git.clone(url=source_url, branch="0.1") as repo: assert_version(repo, BRANCH_TO_REVISION_MAP["0.1"]) spy_clone.assert_called_once() spy_get_transport_and_path.assert_called_with(location=source_url, ) spy_get_transport_and_path.assert_called_once()
def test_git_clone_default_branch_head( source_url: str, remote_refs: FetchPackResult, remote_default_ref: bytes, mocker: MockerFixture, ): spy = mocker.spy(Git, "_clone") spy_legacy = mocker.spy(Git, "_clone_legacy") with Git.clone(url=source_url) as repo: assert remote_refs.refs[remote_default_ref] == repo.head() spy_legacy.assert_not_called() spy.assert_called()
def test_system_git_fallback_on_http_401( mocker: MockerFixture, source_url: str, ) -> None: spy = mocker.spy(Git, "_clone_legacy") mocker.patch.object(Git, "_clone", side_effect=HTTPUnauthorized(None, None)) with Git.clone(url=source_url, branch="0.1") as repo: path = Path(repo.path) assert_version(repo, BRANCH_TO_REVISION_MAP["0.1"]) spy.assert_called_with( url="https://github.com/python-poetry/test-fixture-vcs-repository.git", target=path, refspec=GitRefSpec(branch="0.1", revision=None, tag=None, ref=b"HEAD"), ) spy.assert_called_once()
def test_system_git_called_when_configured( mocker: MockerFixture, source_url: str, use_system_git_client: None) -> None: spy_legacy = mocker.spy(Git, "_clone_legacy") spy = mocker.spy(Git, "_clone") with Git.clone(url=source_url, branch="0.1") as repo: path = Path(repo.path) assert_version(repo, BRANCH_TO_REVISION_MAP["0.1"]) spy.assert_not_called() spy_legacy.assert_called_once() spy_legacy.assert_called_with( url=source_url, target=path, refspec=GitRefSpec(branch="0.1", revision=None, tag=None, ref=b"HEAD"), )
def test_configured_repository_http_auth( mocker: MockerFixture, source_url: str, config: Config ) -> None: from poetry.vcs.git import backend spy_clone_legacy = mocker.spy(Git, "_clone_legacy") spy_get_transport_and_path = mocker.spy(backend, "get_transport_and_path") config.merge( { "repositories": {"git-repo": {"url": source_url}}, "http-basic": { "git-repo": { "username": GIT_USERNAME, "password": GIT_PASSWORD, } }, } ) dummy_git_config = ConfigFile() mocker.patch( "poetry.vcs.git.backend.Repo.get_config_stack", return_value=dummy_git_config, ) mocker.patch( "poetry.vcs.git.backend.get_default_authenticator", return_value=Authenticator(config=config), ) with Git.clone(url=source_url, branch="0.1") as repo: assert_version(repo, BRANCH_TO_REVISION_MAP["0.1"]) spy_clone_legacy.assert_not_called() spy_get_transport_and_path.assert_called_with( location=source_url, config=dummy_git_config, username=GIT_USERNAME, password=GIT_PASSWORD, ) spy_get_transport_and_path.assert_called_once()
def install_git(self, package: Package) -> None: from poetry.core.packages.package import Package from poetry.vcs.git import Git source = Git.clone( url=package.source_url, source_root=self._env.path / "src", revision=package.source_resolved_reference or package.source_reference, ) # Now we just need to install from the source directory pkg = Package(package.name, package.version) pkg._source_type = "directory" pkg._source_url = str(source.path) pkg.develop = package.develop self.install_directory(pkg)
def test_git_clone_branch(source_url: str, remote_refs: FetchPackResult, branch: str) -> None: with Git.clone(url=source_url, branch=branch) as repo: assert_version(repo, BRANCH_TO_REVISION_MAP[branch])
def search_for_vcs(self, dependency: VCSDependency) -> List[Package]: """ Search for the specifications that match the given VCS dependency. Basically, we clone the repository in a temporary directory and get the information we need by checking out the specified reference. """ if dependency.vcs != 'git': raise ValueError(f'Unsupported VCS dependency {dependency.vcs}') tmp_dir = Path(mkdtemp(prefix=f'pypoetry-git-{dependency.name}')) try: git = Git() git.clone(dependency.source, tmp_dir) git.checkout(dependency.reference, tmp_dir) revision = git.rev_parse(dependency.reference, tmp_dir).strip() if dependency.tag or dependency.rev: revision = dependency.reference poetry = TomlFile(tmp_dir / 'pyproject.toml') if poetry.exists(): # If a pyproject.toml file exists # We use it to get the information we need info = poetry.read() name = info['package']['name'] version = info['package']['version'] package = Package(name, version, version) for req_name, req_constraint in info['dependencies'].items(): package.add_dependency(req_name, req_constraint) else: # We need to use setup.py here # to figure the information we need # We need to place ourselves in the proper # folder for it to work current_dir = os.getcwd() os.chdir(tmp_dir.as_posix()) try: venv = Venv.create() output = venv.run('python', 'setup.py', '--name', '--version') output = output.split('\n') name = output[-3] version = output[-2] package = Package(name, version, version) # Figure out a way to get requirements except Exception: raise finally: os.chdir(current_dir) package.source_type = 'git' package.source_url = dependency.source package.source_reference = revision except Exception: raise finally: shutil.rmtree(tmp_dir.as_posix()) return [package]
def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] """ Search for the specifications that match the given VCS dependency. Basically, we clone the repository in a temporary directory and get the information we need by checking out the specified reference. """ if dependency.vcs != "git": raise ValueError("Unsupported VCS dependency {}".format(dependency.vcs)) tmp_dir = Path(mkdtemp(prefix="pypoetry-git-{}".format(dependency.name))) try: git = Git() git.clone(dependency.source, tmp_dir) git.checkout(dependency.reference, tmp_dir) revision = git.rev_parse(dependency.reference, tmp_dir).strip() if dependency.tag or dependency.rev: revision = dependency.reference pyproject = TomlFile(tmp_dir / "pyproject.toml") pyproject_content = None has_poetry = False if pyproject.exists(): pyproject_content = pyproject.read() has_poetry = ( "tool" in pyproject_content and "poetry" in pyproject_content["tool"] ) if pyproject_content and has_poetry: # If a pyproject.toml file exists # We use it to get the information we need info = pyproject_content["tool"]["poetry"] name = info["name"] version = info["version"] package = Package(name, version, version) package.source_type = dependency.vcs package.source_url = dependency.source package.source_reference = dependency.reference for req_name, req_constraint in info["dependencies"].items(): if req_name == "python": package.python_versions = req_constraint continue package.add_dependency(req_name, req_constraint) else: # We need to use setup.py here # to figure the information we need # We need to place ourselves in the proper # folder for it to work venv = Venv.create(self._io) current_dir = os.getcwd() os.chdir(tmp_dir.as_posix()) try: venv.run("python", "setup.py", "egg_info") # Sometimes pathlib will fail on recursive # symbolic links, so we need to workaround it # and use the glob module instead. # Note that this does not happen with pathlib2 # so it's safe to use it for Python < 3.4. if PY35: egg_info = next( Path(p) for p in glob.glob( os.path.join(str(tmp_dir), "**", "*.egg-info"), recursive=True, ) ) else: egg_info = next(tmp_dir.glob("**/*.egg-info")) meta = pkginfo.UnpackedSDist(str(egg_info)) if meta.requires_dist: reqs = list(meta.requires_dist) else: reqs = [] requires = egg_info / "requires.txt" if requires.exists(): with requires.open() as f: reqs = parse_requires(f.read()) package = Package(meta.name, meta.version) for req in reqs: dep = dependency_from_pep_508(req) if dep.in_extras: for extra in dep.in_extras: if extra not in package.extras: package.extras[extra] = [] package.extras[extra].append(dep) package.requires.append(dep) except Exception: raise finally: os.chdir(current_dir) package.source_type = "git" package.source_url = dependency.source package.source_reference = revision except Exception: raise finally: shutil.rmtree(tmp_dir.as_posix()) if dependency.name != package.name: # For now, the dependency's name must match the actual package's name raise RuntimeError( "The dependency name for {} does not match the actual package's name: {}".format( dependency.name, package.name ) ) if dependency.extras: for extra in dependency.extras: if extra in package.extras: for dep in package.extras[extra]: dep.activate() return [package]
def test_git_clone_when_branch_is_ref(source_url: str) -> None: with Git.clone(url=source_url, branch="refs/heads/0.1") as repo: assert_version(repo, BRANCH_TO_REVISION_MAP["0.1"])
def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] """ Search for the specifications that match the given VCS dependency. Basically, we clone the repository in a temporary directory and get the information we need by checking out the specified reference. """ if dependency.vcs != 'git': raise ValueError('Unsupported VCS dependency {}'.format( dependency.vcs)) tmp_dir = Path( mkdtemp(prefix='pypoetry-git-{}'.format(dependency.name))) try: git = Git() git.clone(dependency.source, tmp_dir) git.checkout(dependency.reference, tmp_dir) revision = git.rev_parse(dependency.reference, tmp_dir).strip() if dependency.tag or dependency.rev: revision = dependency.reference pyproject = TomlFile(tmp_dir / 'pyproject.toml') pyproject_content = None has_poetry = False if pyproject.exists(): pyproject_content = pyproject.read(True) has_poetry = ('tool' in pyproject_content and 'poetry' in pyproject_content['tool']) if pyproject_content and has_poetry: # If a pyproject.toml file exists # We use it to get the information we need info = pyproject_content['tool']['poetry'] name = info['name'] version = info['version'] package = Package(name, version, version) package.source_type = dependency.vcs package.source_url = dependency.source package.source_reference = dependency.reference for req_name, req_constraint in info['dependencies'].items(): if req_name == 'python': package.python_versions = req_constraint continue package.add_dependency(req_name, req_constraint) else: # We need to use setup.py here # to figure the information we need # We need to place ourselves in the proper # folder for it to work venv = Venv.create(self._io) current_dir = os.getcwd() os.chdir(tmp_dir.as_posix()) try: venv.run('python', 'setup.py', 'egg_info') egg_info = list(tmp_dir.glob('*.egg-info'))[0] meta = pkginfo.UnpackedSDist(str(egg_info)) if meta.requires_dist: reqs = list(meta.requires_dist) else: reqs = [] requires = egg_info / 'requires.txt' if requires.exists(): with requires.open() as f: reqs = parse_requires(f.read()) package = Package(meta.name, meta.version) for req in reqs: package.requires.append(dependency_from_pep_508(req)) except Exception: raise finally: os.chdir(current_dir) package.source_type = 'git' package.source_url = dependency.source package.source_reference = revision except Exception: raise finally: shutil.rmtree(tmp_dir.as_posix()) return [package]
def test_git_clone_multiple_times(source_url: str, remote_refs: FetchPackResult) -> None: for revision in REVISION_TO_VERSION_MAP: with Git.clone(url=source_url, revision=revision) as repo: assert_version(repo, revision)
def test_git_clone_revision_is_tag(source_url: str, remote_refs: FetchPackResult, revision: str, expected_revision: str) -> None: with Git.clone(url=source_url, revision=revision) as repo: assert_version(repo, expected_revision)
def test_git_clone_revision_is_ref(source_url: str, remote_refs: FetchPackResult) -> None: with Git.clone(url=source_url, revision="refs/heads/0.1") as repo: assert_version(repo, BRANCH_TO_REVISION_MAP["0.1"])
def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] """ Search for the specifications that match the given VCS dependency. Basically, we clone the repository in a temporary directory and get the information we need by checking out the specified reference. """ if dependency.vcs != "git": raise ValueError("Unsupported VCS dependency {}".format( dependency.vcs)) tmp_dir = Path( mkdtemp(prefix="pypoetry-git-{}".format(dependency.name))) try: git = Git() git.clone(dependency.source, tmp_dir) git.checkout(dependency.reference, tmp_dir) revision = git.rev_parse(dependency.reference, tmp_dir).strip() if dependency.tag or dependency.rev: revision = dependency.reference pyproject = TomlFile(tmp_dir / "pyproject.toml") pyproject_content = None has_poetry = False if pyproject.exists(): pyproject_content = pyproject.read() has_poetry = ("tool" in pyproject_content and "poetry" in pyproject_content["tool"]) if pyproject_content and has_poetry: # If a pyproject.toml file exists # We use it to get the information we need info = pyproject_content["tool"]["poetry"] name = info["name"] version = info["version"] package = Package(name, version, version) package.source_type = dependency.vcs package.source_url = dependency.source package.source_reference = dependency.reference for req_name, req_constraint in info["dependencies"].items(): if req_name == "python": package.python_versions = req_constraint continue package.add_dependency(req_name, req_constraint) else: # We need to use setup.py here # to figure the information we need # We need to place ourselves in the proper # folder for it to work venv = Venv.create(self._io) current_dir = os.getcwd() os.chdir(tmp_dir.as_posix()) try: venv.run("python", "setup.py", "egg_info") # Sometimes pathlib will fail on recursive # symbolic links, so we need to workaround it # and use the glob module instead. # Note that this does not happen with pathlib2 # so it's safe to use it for Python < 3.4. if PY35: egg_info = next( Path(p) for p in glob.glob( os.path.join(str(tmp_dir), "**", "*.egg-info"), recursive=True, )) else: egg_info = next(tmp_dir.glob("**/*.egg-info")) meta = pkginfo.UnpackedSDist(str(egg_info)) if meta.requires_dist: reqs = list(meta.requires_dist) else: reqs = [] requires = egg_info / "requires.txt" if requires.exists(): with requires.open() as f: reqs = parse_requires(f.read()) package = Package(meta.name, meta.version) for req in reqs: dep = dependency_from_pep_508(req) if dep.in_extras: for extra in dep.in_extras: if extra not in package.extras: package.extras[extra] = [] package.extras[extra].append(dep) package.requires.append(dep) except Exception: raise finally: os.chdir(current_dir) package.source_type = "git" package.source_url = dependency.source package.source_reference = revision except Exception: raise finally: shutil.rmtree(tmp_dir.as_posix()) if dependency.name != package.name: # For now, the dependency's name must match the actual package's name raise RuntimeError( "The dependency name for {} does not match the actual package's name: {}" .format(dependency.name, package.name)) if dependency.extras: for extra in dependency.extras: if extra in package.extras: for dep in package.extras[extra]: dep.activate() return [package]
def test_git_clone_tag(source_url: str, remote_refs: FetchPackResult, tag: str) -> None: with Git.clone(url=source_url, tag=tag) as repo: assert_version(repo, TAG_TO_REVISION_MAP[tag])