Skip to content

Commit

Permalink
feat(ux): improve ux for git specific errors
Browse files Browse the repository at this point in the history
  • Loading branch information
abn committed Jan 22, 2025
1 parent d04b221 commit 52e4331
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 20 deletions.
87 changes: 72 additions & 15 deletions src/poetry/vcs/git/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from dulwich.refs import ANNOTATED_TAG_SUFFIX
from dulwich.repo import Repo

from poetry.console.exceptions import PoetryConsoleError
from poetry.console.exceptions import PoetryRuntimeError
from poetry.utils.authenticator import get_default_authenticator
from poetry.utils.helpers import remove_directory

Expand All @@ -36,6 +36,30 @@
# A relative URL by definition starts with ../ or ./
RELATIVE_SUBMODULE_REGEX = re.compile(r"^\.{1,2}/")

# Common error messages
ERROR_MESSAGE_NOTE = (
"<b>Note:</> This error arises from interacting with "
"the specified vcs source and is likely not a "
"Poetry issue."
)
ERROR_MESSAGE_PROBLEMS_SECTION_START = (
"This issue could be caused by any of the following;\n\n"
"- there are network issues in this environment"
)
ERROR_MESSAGE_BAD_REVISION = (
"- the revision ({revision}) you have specified\n"
" - was misspelled\n"
" - is invalid (must be a sha or symref)\n"
" - is not present on remote"
)
ERROR_MESSAGE_BAD_REMOTE = (
"- the remote ({remote}) you have specified\n"
" - was misspelled\n"
" - does not exist\n"
" - requires credentials that were either not configured or is incorrect\n"
" - is in accessible due to network issues"
)


def is_revision_sha(revision: str | None) -> bool:
return re.match(r"^\b[0-9a-f]{5,40}\b$", revision or "") is not None
Expand Down Expand Up @@ -236,10 +260,15 @@ def _clone_legacy(url: str, refspec: GitRefSpec, target: Path) -> Repo:

try:
SystemGit.clone(url, target)
except CalledProcessError:
raise PoetryConsoleError(
f"Failed to clone {url}, check your git configuration and permissions"
" for this repository."
except CalledProcessError as e:
raise PoetryRuntimeError.create(
reason=f"<error>Failed to clone <info>{url}</>, check your git configuration and permissions for this repository.</>",
exception=e,
info=[
ERROR_MESSAGE_NOTE,
ERROR_MESSAGE_PROBLEMS_SECTION_START,
ERROR_MESSAGE_BAD_REMOTE.format(remote=url),
],
)

if revision:
Expand All @@ -248,8 +277,16 @@ def _clone_legacy(url: str, refspec: GitRefSpec, target: Path) -> Repo:

try:
SystemGit.checkout(revision, target)
except CalledProcessError:
raise PoetryConsoleError(f"Failed to checkout {url} at '{revision}'")
except CalledProcessError as e:
raise PoetryRuntimeError.create(
reason=f"<error>Failed to checkout {url} at '{revision}'.</>",
exception=e,
info=[
ERROR_MESSAGE_NOTE,
ERROR_MESSAGE_PROBLEMS_SECTION_START,
ERROR_MESSAGE_BAD_REVISION.format(revision=revision),
],
)

repo = Repo(str(target))
return repo
Expand All @@ -276,13 +313,28 @@ def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo:
try:
refspec.resolve(remote_refs=remote_refs, repo=local)
except KeyError: # branch / ref does not exist
raise PoetryConsoleError(
f"Failed to clone {url} at '{refspec.key}', verify ref exists on"
" remote."
raise PoetryRuntimeError.create(
reason=f"<error>Failed to clone {url} at '{refspec.key}', verify ref exists on remote.</>",
info=[
ERROR_MESSAGE_NOTE,
ERROR_MESSAGE_PROBLEMS_SECTION_START,
ERROR_MESSAGE_BAD_REVISION.format(revision=refspec.key),
],
)

# ensure local HEAD matches remote
local.refs[b"HEAD"] = remote_refs.refs[b"HEAD"]
try:
# ensure local HEAD matches remote
local.refs[b"HEAD"] = remote_refs.refs[b"HEAD"]
except ValueError:
raise PoetryRuntimeError.create(
reason=f"<error>Failed to clone {url} at '{refspec.key}', verify ref exists on remote.</>",
info=[
ERROR_MESSAGE_NOTE,
ERROR_MESSAGE_PROBLEMS_SECTION_START,
ERROR_MESSAGE_BAD_REVISION.format(revision=refspec.key),
f"\nThis particular error is prevalent when {refspec.key} could not be resolved to a specific commit sha.",
],
)

if refspec.is_ref:
# set ref to current HEAD
Expand Down Expand Up @@ -318,9 +370,14 @@ def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo:
if isinstance(e, AssertionError) and "Invalid object name" not in str(e):
raise

raise PoetryConsoleError(
f"Failed to clone {url} at '{refspec.key}', verify ref exists on"
" remote."
raise PoetryRuntimeError.create(
reason=f"<error>Failed to clone {url} at '{refspec.key}', verify ref exists on remote.</>",
info=[
ERROR_MESSAGE_NOTE,
ERROR_MESSAGE_PROBLEMS_SECTION_START,
ERROR_MESSAGE_BAD_REVISION.format(revision=refspec.key),
],
exception=e,
)

return local
Expand Down
7 changes: 4 additions & 3 deletions src/poetry/vcs/git/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,14 @@ def run(*args: Any, **kwargs: Any) -> None:
git_command = find_git_command()
env = os.environ.copy()
env["GIT_TERMINAL_PROMPT"] = "0"
subprocess.check_call(

subprocess.run(
git_command + list(args),
stderr=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
capture_output=True,
env=env,
text=True,
encoding="utf-8",
check=True,
)

@staticmethod
Expand Down
11 changes: 9 additions & 2 deletions tests/installation/test_chooser.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from packaging.tags import Tag
from poetry.core.packages.package import Package

from poetry.console.exceptions import PoetryRuntimeError
from poetry.installation.chooser import Chooser
from poetry.repositories.legacy_repository import LegacyRepository
from poetry.repositories.pypi_repository import PyPiRepository
Expand Down Expand Up @@ -366,9 +367,15 @@ def test_chooser_throws_an_error_if_package_hashes_do_not_match(

package.files = files

with pytest.raises(RuntimeError) as e:
with pytest.raises(PoetryRuntimeError) as e:
chooser.choose_for(package)
assert files[0]["hash"] in str(e)

reason = f"Downloaded distributions for {package.name} ({package.version}) did not match any known checksums in your lock file."
assert str(e.value) == reason

text = e.value.get_text(debug=True, strip=True)
assert reason in text
assert files[0]["hash"] in text


def test_chooser_md5_remote_fallback_to_sha256_inline_calculation(
Expand Down

0 comments on commit 52e4331

Please sign in to comment.