diff --git a/docs/html/user_guide.rst b/docs/html/user_guide.rst
index 399e561cbcb..6d238908c1a 100644
--- a/docs/html/user_guide.rst
+++ b/docs/html/user_guide.rst
@@ -743,7 +743,10 @@ To setup for bash::
To setup for zsh::
- python -m pip completion --zsh >> ~/.zprofile
+ python -m pip completion --zsh >> ~/.zshrc
+
+ # Requires zsh completion (compinit) to be enabled.
+
To setup for fish::
diff --git a/news/13811.bugfix.rst b/news/13811.bugfix.rst
new file mode 100644
index 00000000000..045fa35ae0e
--- /dev/null
+++ b/news/13811.bugfix.rst
@@ -0,0 +1 @@
+Deduplicate repeated invalid metadata warnings for the same location.
diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py
index aa7c2ebd48e..8c0bbecadab 100644
--- a/src/pip/_internal/index/package_finder.py
+++ b/src/pip/_internal/index/package_finder.py
@@ -1037,7 +1037,7 @@ def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str:
_format_versions(best_candidate_result.all_candidates),
)
- raise DistributionNotFound(f"No matching distribution found for {req}")
+ raise DistributionNotFound(f"No matching distribution found for {name}")
def _should_install_candidate(
candidate: InstallationCandidate | None,
diff --git a/src/pip/_internal/metadata/importlib/_envs.py b/src/pip/_internal/metadata/importlib/_envs.py
index 71a73b7311f..1b46fee03ca 100644
--- a/src/pip/_internal/metadata/importlib/_envs.py
+++ b/src/pip/_internal/metadata/importlib/_envs.py
@@ -54,6 +54,7 @@ class _DistributionFinder:
def __init__(self) -> None:
self._found_names: set[NormalizedName] = set()
+ self._warned_bad_metadata: set[BasePath | None] = set()
def _find_impl(self, location: str) -> Iterator[FoundResult]:
"""Find distributions in a location."""
@@ -69,7 +70,9 @@ def _find_impl(self, location: str) -> Iterator[FoundResult]:
try:
name = get_dist_canonical_name(dist)
except BadMetadata as e:
- logger.warning("Skipping %s due to %s", info_location, e.reason)
+ if info_location not in self._warned_bad_metadata:
+ logger.warning("Skipping %s due to %s", info_location, e.reason)
+ self._warned_bad_metadata.add(info_location)
continue
if name in self._found_names:
continue
diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py
index ede3e6b2b94..b6a7b2eac36 100644
--- a/src/pip/_internal/resolution/resolvelib/factory.py
+++ b/src/pip/_internal/resolution/resolvelib/factory.py
@@ -720,7 +720,9 @@ def _report_single_requirement_conflict(
"requirements.txt"
)
- return DistributionNotFound(f"No matching distribution found for {req}")
+ return DistributionNotFound(
+ f"No matching distribution found for {req.project_name}"
+ )
def _has_any_candidates(self, project_name: str) -> bool:
"""
diff --git a/tests/functional/test_new_resolver.py b/tests/functional/test_new_resolver.py
index b5b26128037..78ecfd8f30e 100644
--- a/tests/functional/test_new_resolver.py
+++ b/tests/functional/test_new_resolver.py
@@ -298,7 +298,7 @@ def test_new_resolver_no_dist_message(script: PipTestEnvironment) -> None:
assert (
"Could not find a version that satisfies the requirement B" in result.stderr
), str(result)
- assert "No matching distribution found for B" in result.stderr, str(result)
+ assert "no matching distribution found for b" in result.stderr.lower(), str(result)
def test_new_resolver_installs_editable(script: PipTestEnvironment) -> None: