Skip to content

Commit 52e4331

Browse files
committed
feat(ux): improve ux for git specific errors
1 parent d04b221 commit 52e4331

File tree

3 files changed

+85
-20
lines changed

3 files changed

+85
-20
lines changed

src/poetry/vcs/git/backend.py

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from dulwich.refs import ANNOTATED_TAG_SUFFIX
2222
from dulwich.repo import Repo
2323

24-
from poetry.console.exceptions import PoetryConsoleError
24+
from poetry.console.exceptions import PoetryRuntimeError
2525
from poetry.utils.authenticator import get_default_authenticator
2626
from poetry.utils.helpers import remove_directory
2727

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

39+
# Common error messages
40+
ERROR_MESSAGE_NOTE = (
41+
"<b>Note:</> This error arises from interacting with "
42+
"the specified vcs source and is likely not a "
43+
"Poetry issue."
44+
)
45+
ERROR_MESSAGE_PROBLEMS_SECTION_START = (
46+
"This issue could be caused by any of the following;\n\n"
47+
"- there are network issues in this environment"
48+
)
49+
ERROR_MESSAGE_BAD_REVISION = (
50+
"- the revision ({revision}) you have specified\n"
51+
" - was misspelled\n"
52+
" - is invalid (must be a sha or symref)\n"
53+
" - is not present on remote"
54+
)
55+
ERROR_MESSAGE_BAD_REMOTE = (
56+
"- the remote ({remote}) you have specified\n"
57+
" - was misspelled\n"
58+
" - does not exist\n"
59+
" - requires credentials that were either not configured or is incorrect\n"
60+
" - is in accessible due to network issues"
61+
)
62+
3963

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

237261
try:
238262
SystemGit.clone(url, target)
239-
except CalledProcessError:
240-
raise PoetryConsoleError(
241-
f"Failed to clone {url}, check your git configuration and permissions"
242-
" for this repository."
263+
except CalledProcessError as e:
264+
raise PoetryRuntimeError.create(
265+
reason=f"<error>Failed to clone <info>{url}</>, check your git configuration and permissions for this repository.</>",
266+
exception=e,
267+
info=[
268+
ERROR_MESSAGE_NOTE,
269+
ERROR_MESSAGE_PROBLEMS_SECTION_START,
270+
ERROR_MESSAGE_BAD_REMOTE.format(remote=url),
271+
],
243272
)
244273

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

249278
try:
250279
SystemGit.checkout(revision, target)
251-
except CalledProcessError:
252-
raise PoetryConsoleError(f"Failed to checkout {url} at '{revision}'")
280+
except CalledProcessError as e:
281+
raise PoetryRuntimeError.create(
282+
reason=f"<error>Failed to checkout {url} at '{revision}'.</>",
283+
exception=e,
284+
info=[
285+
ERROR_MESSAGE_NOTE,
286+
ERROR_MESSAGE_PROBLEMS_SECTION_START,
287+
ERROR_MESSAGE_BAD_REVISION.format(revision=revision),
288+
],
289+
)
253290

254291
repo = Repo(str(target))
255292
return repo
@@ -276,13 +313,28 @@ def _clone(cls, url: str, refspec: GitRefSpec, target: Path) -> Repo:
276313
try:
277314
refspec.resolve(remote_refs=remote_refs, repo=local)
278315
except KeyError: # branch / ref does not exist
279-
raise PoetryConsoleError(
280-
f"Failed to clone {url} at '{refspec.key}', verify ref exists on"
281-
" remote."
316+
raise PoetryRuntimeError.create(
317+
reason=f"<error>Failed to clone {url} at '{refspec.key}', verify ref exists on remote.</>",
318+
info=[
319+
ERROR_MESSAGE_NOTE,
320+
ERROR_MESSAGE_PROBLEMS_SECTION_START,
321+
ERROR_MESSAGE_BAD_REVISION.format(revision=refspec.key),
322+
],
282323
)
283324

284-
# ensure local HEAD matches remote
285-
local.refs[b"HEAD"] = remote_refs.refs[b"HEAD"]
325+
try:
326+
# ensure local HEAD matches remote
327+
local.refs[b"HEAD"] = remote_refs.refs[b"HEAD"]
328+
except ValueError:
329+
raise PoetryRuntimeError.create(
330+
reason=f"<error>Failed to clone {url} at '{refspec.key}', verify ref exists on remote.</>",
331+
info=[
332+
ERROR_MESSAGE_NOTE,
333+
ERROR_MESSAGE_PROBLEMS_SECTION_START,
334+
ERROR_MESSAGE_BAD_REVISION.format(revision=refspec.key),
335+
f"\nThis particular error is prevalent when {refspec.key} could not be resolved to a specific commit sha.",
336+
],
337+
)
286338

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

321-
raise PoetryConsoleError(
322-
f"Failed to clone {url} at '{refspec.key}', verify ref exists on"
323-
" remote."
373+
raise PoetryRuntimeError.create(
374+
reason=f"<error>Failed to clone {url} at '{refspec.key}', verify ref exists on remote.</>",
375+
info=[
376+
ERROR_MESSAGE_NOTE,
377+
ERROR_MESSAGE_PROBLEMS_SECTION_START,
378+
ERROR_MESSAGE_BAD_REVISION.format(revision=refspec.key),
379+
],
380+
exception=e,
324381
)
325382

326383
return local

src/poetry/vcs/git/system.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,14 @@ def run(*args: Any, **kwargs: Any) -> None:
4040
git_command = find_git_command()
4141
env = os.environ.copy()
4242
env["GIT_TERMINAL_PROMPT"] = "0"
43-
subprocess.check_call(
43+
44+
subprocess.run(
4445
git_command + list(args),
45-
stderr=subprocess.DEVNULL,
46-
stdout=subprocess.DEVNULL,
46+
capture_output=True,
4747
env=env,
4848
text=True,
4949
encoding="utf-8",
50+
check=True,
5051
)
5152

5253
@staticmethod

tests/installation/test_chooser.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from packaging.tags import Tag
99
from poetry.core.packages.package import Package
1010

11+
from poetry.console.exceptions import PoetryRuntimeError
1112
from poetry.installation.chooser import Chooser
1213
from poetry.repositories.legacy_repository import LegacyRepository
1314
from poetry.repositories.pypi_repository import PyPiRepository
@@ -366,9 +367,15 @@ def test_chooser_throws_an_error_if_package_hashes_do_not_match(
366367

367368
package.files = files
368369

369-
with pytest.raises(RuntimeError) as e:
370+
with pytest.raises(PoetryRuntimeError) as e:
370371
chooser.choose_for(package)
371-
assert files[0]["hash"] in str(e)
372+
373+
reason = f"Downloaded distributions for {package.name} ({package.version}) did not match any known checksums in your lock file."
374+
assert str(e.value) == reason
375+
376+
text = e.value.get_text(debug=True, strip=True)
377+
assert reason in text
378+
assert files[0]["hash"] in text
372379

373380

374381
def test_chooser_md5_remote_fallback_to_sha256_inline_calculation(

0 commit comments

Comments
 (0)