Skip to content

Commit

Permalink
git: allow users to fall back to system git
Browse files Browse the repository at this point in the history
This change introduces the config option
`experimental.system-git-client` defaulting to `false`. When set to
`true`, the subprocess git client implementation is used when cloning a
remote repository.

This option will be removed in a future release.
  • Loading branch information
abn committed Apr 29, 2022
1 parent 2f8f18c commit 4a615ce
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 13 deletions.
18 changes: 18 additions & 0 deletions docs/dependency-specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,24 @@ To use an SSH connection, for example in the case of private repositories, use t
requests = { git = "git@github.com:requests/requests.git" }
```

{{% note %}}
With Poetry 1.2 releases, the default git client used is [Dulwich](https://www.dulwich.io/). We
fall back to legacy system git client implementation in cases where [gitcredentials](https://git-scm.com/docs/gitcredentials)
are used. This fallback will be removed in a future release where username/password authentication
can be better supported natively.

In cases where you encounter issues with the default implementation that used to work prior to
Poetry 1.2, you may wish to explicitly configure the use of the system git client via a shell
subprocess call.

```bash
poetry config experimental.system-git-client true
```

Keep in mind however, that doing so will surface bugs that existed in versions prior to 1.2 which
were caused due to the use of the system git client.
{{% /note %}}

## `path` dependencies

To depend on a library located in a local directory or file,
Expand Down
3 changes: 2 additions & 1 deletion src/poetry/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class Config:
"options": {"always-copy": False, "system-site-packages": False},
"prefer-active-python": False,
},
"experimental": {"new-installer": True},
"experimental": {"new-installer": True, "system-git-client": False},
"installer": {"parallel": True, "max-workers": None},
}

Expand Down Expand Up @@ -141,6 +141,7 @@ def _get_normalizer(name: str) -> Callable:
"virtualenvs.options.system-site-packages",
"virtualenvs.options.prefer-active-python",
"experimental.new-installer",
"experimental.system-git-client",
"installer.parallel",
}:
return boolean_normalizer
Expand Down
36 changes: 26 additions & 10 deletions src/poetry/vcs/git/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ def _clone_legacy(url: str, refspec: GitRefSpec, target: Path) -> Repo:
"""
from poetry.vcs.git.system import SystemGit

logger.debug("Cloning '%s' using system git client", url)

if target.exists():
safe_rmtree(path=target, ignore_errors=True)

Expand Down Expand Up @@ -313,6 +315,22 @@ def _clone_submodules(cls, repo: Repo) -> None:
and not path_absolute.joinpath(".git").is_dir(),
)

@staticmethod
def is_using_legacy_client() -> bool:
from poetry.factory import Factory

return (
Factory.create_config()
.get("experimental", {})
.get("system-git-client", False)
)

@staticmethod
def get_default_source_root() -> Path:
from poetry.factory import Factory

return Path(Factory.create_config().get("cache-dir")) / "src"

@classmethod
def clone(
cls,
Expand All @@ -324,11 +342,7 @@ def clone(
source_root: Path | None = None,
clean: bool = False,
) -> Repo:
if not source_root:
from poetry.factory import Factory

source_root = Path(Factory.create_config().get("cache-dir")) / "src"

source_root = source_root or cls.get_default_source_root()
source_root.mkdir(parents=True, exist_ok=True)

name = name or cls.get_name_from_source_url(url=url)
Expand Down Expand Up @@ -358,8 +372,10 @@ def clone(
return current_repo

try:
local = cls._clone(url=url, refspec=refspec, target=target)
cls._clone_submodules(repo=local)
if not cls.is_using_legacy_client():
local = cls._clone(url=url, refspec=refspec, target=target)
cls._clone_submodules(repo=local)
return local
except HTTPUnauthorized:
# we do this here to handle http authenticated repositories as dulwich
# does not currently support using credentials from git-credential helpers.
Expand All @@ -369,10 +385,10 @@ def clone(
# without additional configuration or changes for existing projects that
# use http basic auth credentials.
logger.debug(
"Unable to fetch from private repository '{%s}', falling back to"
"Unable to fetch from private repository '%s', falling back to"
" system git",
url,
)
local = cls._clone_legacy(url=url, refspec=refspec, target=target)

return local
# fallback to legacy git client
return cls._clone_legacy(url=url, refspec=refspec, target=target)
3 changes: 3 additions & 0 deletions tests/console/commands/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def test_list_displays_default_value_if_not_set(
venv_path = json.dumps(os.path.join("{cache-dir}", "virtualenvs"))
expected = f"""cache-dir = {cache_dir}
experimental.new-installer = true
experimental.system-git-client = false
installer.max-workers = null
installer.parallel = true
virtualenvs.create = true
Expand All @@ -75,6 +76,7 @@ def test_list_displays_set_get_setting(
venv_path = json.dumps(os.path.join("{cache-dir}", "virtualenvs"))
expected = f"""cache-dir = {cache_dir}
experimental.new-installer = true
experimental.system-git-client = false
installer.max-workers = null
installer.parallel = true
virtualenvs.create = false
Expand Down Expand Up @@ -123,6 +125,7 @@ def test_list_displays_set_get_local_setting(
venv_path = json.dumps(os.path.join("{cache-dir}", "virtualenvs"))
expected = f"""cache-dir = {cache_dir}
experimental.new-installer = true
experimental.system-git-client = false
installer.max-workers = null
installer.parallel = true
virtualenvs.create = false
Expand Down
39 changes: 37 additions & 2 deletions tests/integration/test_utils_vcs_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ def setup(config: Config) -> None:
}


@pytest.fixture
def use_system_git_client(config: Config) -> None:
config.merge({"experimental": {"system-git-client": True}})


@pytest.fixture(scope="module")
def source_url() -> str:
return "/~https://github.com/python-poetry/test-fixture-vcs-repository.git"
Expand Down Expand Up @@ -104,11 +109,20 @@ def remote_default_branch(remote_default_ref: bytes) -> str:


def test_git_clone_default_branch_head(
source_url: str, remote_refs: FetchPackResult, remote_default_ref: bytes
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_git_clone_fails_for_non_existent_branch(source_url: str):
branch = uuid.uuid4().hex
Expand Down Expand Up @@ -208,7 +222,8 @@ def test_git_clone_clones_submodules(source_url: str) -> None:


def test_system_git_fallback_on_http_401(
mocker: MockerFixture, source_url: str
mocker: MockerFixture,
source_url: str,
) -> None:
spy = mocker.spy(Git, "_clone_legacy")
mocker.patch.object(Git, "_clone", side_effect=HTTPUnauthorized(None, None))
Expand All @@ -223,3 +238,23 @@ def test_system_git_fallback_on_http_401(
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"),
)

0 comments on commit 4a615ce

Please sign in to comment.