Repository: pypa/pip
Pull request #10238 (2021-07-30 06:45:36)

Avoid deprecation warning from Tomli
~~Universal newlines are not TOML compatible.~~

~~See e.g. https://github.com/toml-lang/toml/issues/837 for context.~~ EDIT: this is no longer relevant to this PR given the new title and current changes.

uranusjr: So if I understand this correctly, this only affect string literals? If that’s the case, this can probably not too urgent and can wait until 21.3 (since long string literals are not at all common in `pyproject.toml` right now). Otherwise we should backport this to 21.2.
hukkin: This mostly just makes the parser throw an exception if CR characters are used as line endings, anywhere in the document, as that is invalid TOML. I don't think this is in any way urgent at all but is more _technically correct_, :smile: FWIW.
uranusjr: Oh, so I’m thinking bckwards; the current implementation silently coerce CR (due to universal newlines), which is technically incorrect? If that’s the case, maybe we should keep doing that since banning CR all the sudden can be too disruptive to Windows users.
hukkin: Yeah current implementation converts CRs (and CRLFs) to LFs. The correct behavior is to error, because CR is not an allowed line ending in TOML.

> If that’s the case, maybe we should keep doing that

That should also work. Note that I have deprecated text file objects as `tomli.load` input (in favor of binary file objects) because TOML spec is not nearly as liberal about encoding and newlines as Python text file objects. For 100% compliancy TOML requires UTF-8 and disabling universal newlines, which are incredibly easy to forget to configure. Thus, if you want to avoid a deprecation warning in tomli v1.2.0+ you should move from 
```python
with open(pyproject_toml, encoding="utf-8") as f:
    pp_toml = tomli.load(f)
```
to something like
```python
with open(pyproject_toml, encoding="utf-8") as f:
    pp_toml = tomli.loads(f.read())
```

> since banning CR all the sudden can be too disruptive to Windows users.

Banning CR should not disrupt Windows users (Windows uses CRLF which will still work fine). My understanding is that CR as line ending is used by very old Mac OSes (pre Mac OS X (released 2001)) and other exotic legacy systems.

pfmoore: So I'm confused. On Windows, text files (which TOML files need to be considered as, because a huge part of [the reason for using TOML](https://www.python.org/dev/peps/pep-0518/#overview-of-file-formats-considered) as the format for Python packaging is that it's human readable/editable) can use LF or CRLF line endings, as text editors typically produce either. And of course projects developed on Windows being built on Unix could have pyproject.toml with either line ending.

I'd say that `pyproject.toml` must allow LF and CRLF interchangeably as line endings on all platforms. If that means we're now technically using a variant of TOML, then I think we need to explicitly note that fact in PEP 518.

If the prohibition is *just* about bare CR, not CRs that are part of a CRLF pair, then the above is irrelevant and you can ignore me (except as a warning to be careful how you present this information, if you want to avoid scaring people!)
hukkin: > If the prohibition is just about bare CR, not CRs that are part of a CRLF pair, then the above is irrelevant and you can ignore me (except as a warning to be careful how you present this information, if you want to avoid scaring people!)

This is only about bare CR. Sorry, didn't mean to scare anyone :smile: 

pradyunsg: To communicate with sufficient clarity: I do not intend to block the 21.3 release on this PR, since this doesn't seem to be a show-stopper issue for the release. To that end, I've gone ahead and dropped this from the release milestone. :)


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 74ba34c6f..024ff355d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -42,7 +42,7 @@ jobs:
               - "src/pip/_vendor/**"
               - "pyproject.toml"
             tests:
-              # Anything that's touching testable stuff
+              # Anything that's touching code-related stuff
               - ".github/workflows/ci.yml"
               - "src/**"
               - "tests/**"
diff --git a/.github/workflows/lock-threads.yml b/.github/workflows/lock-threads.yml
index 1841ce1f7..990440dd6 100644
--- a/.github/workflows/lock-threads.yml
+++ b/.github/workflows/lock-threads.yml
@@ -14,6 +14,7 @@ concurrency:
 
 jobs:
   action:
+    if: github.repository_owner == 'pypa'
     runs-on: ubuntu-latest
     steps:
       - uses: dessant/lock-threads@v3
diff --git a/LICENSE.txt b/LICENSE.txt
index 00addc272..8e7b65eaf 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2008-2021 The pip developers (see AUTHORS.txt file)
+Copyright (c) 2008-present The pip developers (see AUTHORS.txt file)
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 8c71de35d..fa3a7390c 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,4 +1,4 @@
-sphinx ~= 4.2
+sphinx ~= 4.2, != 4.4.0
 towncrier
 furo
 myst_parser
diff --git a/news/10617.bugfix.rst b/news/10617.bugfix.rst
new file mode 100755
index 000000000..8536d5e63
--- /dev/null
+++ b/news/10617.bugfix.rst
@@ -0,0 +1,2 @@
+Prevent pip from installing yanked releases unless
+explicitely pinned via the ``==`` or ``===`` operators.
diff --git a/news/10646.feature.rst b/news/10646.feature.rst
new file mode 100644
index 000000000..645fdfb51
--- /dev/null
+++ b/news/10646.feature.rst
@@ -0,0 +1,1 @@
+``pip install <tab>`` autocompletes paths.
diff --git a/news/10703.feature.rst b/news/10703.feature.rst
new file mode 100644
index 000000000..4094f4140
--- /dev/null
+++ b/news/10703.feature.rst
@@ -0,0 +1,1 @@
+Start using Rich for presenting error messages in a consistent format.
diff --git a/news/8819.feature.rst b/news/8819.feature.rst
new file mode 100644
index 000000000..d539730e3
--- /dev/null
+++ b/news/8819.feature.rst
@@ -0,0 +1,1 @@
+Forward pip's verbosity configuration to VCS tools to control their output accordingly.
diff --git a/news/eb0afbc2-59df-40a3-b7bd-325305c3e2e7.trivial.rst b/news/eb0afbc2-59df-40a3-b7bd-325305c3e2e7.trivial.rst
new file mode 100644
index 000000000..e69de29bb
diff --git a/news/requests.vendor.rst b/news/requests.vendor.rst
new file mode 100644
index 000000000..4e623aa3e
--- /dev/null
+++ b/news/requests.vendor.rst
@@ -0,0 +1,1 @@
+Upgrade Requests to 2.27.1
diff --git a/news/urllib3.vendor.rst b/news/urllib3.vendor.rst
new file mode 100644
index 000000000..9d5e1f169
--- /dev/null
+++ b/news/urllib3.vendor.rst
@@ -0,0 +1,1 @@
+Upgrade urllib3 to 1.26.8
diff --git a/noxfile.py b/noxfile.py
index 85fccf180..5d5f426c5 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -110,7 +110,14 @@ def test(session: nox.Session) -> None:
     # Run the tests
     #   LC_CTYPE is set to get UTF-8 output inside of the subprocesses that our
     #   tests use.
-    session.run("pytest", *arguments, env={"LC_CTYPE": "en_US.UTF-8"})
+    session.run(
+        "pytest",
+        *arguments,
+        env={
+            "LC_CTYPE": "en_US.UTF-8",
+            "SETUPTOOLS_USE_DISTUTILS": "stdlib",
+        },
+    )
 
 
 @nox.session
diff --git a/src/pip/_internal/cli/autocompletion.py b/src/pip/_internal/cli/autocompletion.py
index 3cad14860..226fe84dc 100644
--- a/src/pip/_internal/cli/autocompletion.py
+++ b/src/pip/_internal/cli/autocompletion.py
@@ -59,6 +59,14 @@ def autocomplete() -> None:
                     print(dist)
                 sys.exit(1)
 
+        should_list_installables = (
+            not current.startswith("-") and subcommand_name == "install"
+        )
+        if should_list_installables:
+            for path in auto_complete_paths(current, "path"):
+                print(path)
+            sys.exit(1)
+
         subcommand = create_command(subcommand_name)
 
         for opt in subcommand.parser.option_list_all:
@@ -138,7 +146,7 @@ def auto_complete_paths(current: str, completion_type: str) -> Iterable[str]:
     starting with ``current``.
 
     :param current: The word to be completed
-    :param completion_type: path completion type(`file`, `path` or `dir`)i
+    :param completion_type: path completion type(``file``, ``path`` or ``dir``)
     :return: A generator of regular files and/or directories
     """
     directory, filename = os.path.split(current)
diff --git a/src/pip/_internal/cli/base_command.py b/src/pip/_internal/cli/base_command.py
index 5c41488ce..81c443adf 100644
--- a/src/pip/_internal/cli/base_command.py
+++ b/src/pip/_internal/cli/base_command.py
@@ -165,16 +165,16 @@ class Command(CommandContextMixIn):
                     status = run_func(*args)
                     assert isinstance(status, int)
                     return status
-                except PreviousBuildDirError as exc:
-                    logger.critical(str(exc))
+                except DiagnosticPipError as exc:
+                    logger.error("[present-diagnostic]", exc)
                     logger.debug("Exception information:", exc_info=True)
 
-                    return PREVIOUS_BUILD_DIR_ERROR
-                except DiagnosticPipError as exc:
+                    return ERROR
+                except PreviousBuildDirError as exc:
                     logger.critical(str(exc))
                     logger.debug("Exception information:", exc_info=True)
 
-                    return ERROR
+                    return PREVIOUS_BUILD_DIR_ERROR
                 except (
                     InstallationError,
                     UninstallationError,
diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py
index 1607c78b1..177b3dbc2 100644
--- a/src/pip/_internal/cli/req_command.py
+++ b/src/pip/_internal/cli/req_command.py
@@ -237,6 +237,7 @@ class RequirementCommand(IndexGroupCommand):
         finder: PackageFinder,
         use_user_site: bool,
         download_dir: Optional[str] = None,
+        verbosity: int = 0,
     ) -> RequirementPreparer:
         """
         Create a RequirementPreparer instance for the given parameters.
@@ -295,6 +296,7 @@ class RequirementCommand(IndexGroupCommand):
             require_hashes=options.require_hashes,
             use_user_site=use_user_site,
             lazy_wheel=lazy_wheel,
+            verbosity=verbosity,
             in_tree_build=in_tree_build,
         )
 
diff --git a/src/pip/_internal/commands/download.py b/src/pip/_internal/commands/download.py
index 7de207f13..233b7e983 100644
--- a/src/pip/_internal/commands/download.py
+++ b/src/pip/_internal/commands/download.py
@@ -113,6 +113,7 @@ class DownloadCommand(RequirementCommand):
             finder=finder,
             download_dir=options.download_dir,
             use_user_site=False,
+            verbosity=self.verbosity,
         )
 
         resolver = self.make_resolver(
diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py
index eedb1ff5d..34e4c2f84 100644
--- a/src/pip/_internal/commands/install.py
+++ b/src/pip/_internal/commands/install.py
@@ -319,6 +319,7 @@ class InstallCommand(RequirementCommand):
                 session=session,
                 finder=finder,
                 use_user_site=options.use_user_site,
+                verbosity=self.verbosity,
             )
             resolver = self.make_resolver(
                 preparer=preparer,
diff --git a/src/pip/_internal/commands/wheel.py b/src/pip/_internal/commands/wheel.py
index cea81ee51..d5b20dc9f 100644
--- a/src/pip/_internal/commands/wheel.py
+++ b/src/pip/_internal/commands/wheel.py
@@ -128,6 +128,7 @@ class WheelCommand(RequirementCommand):
             finder=finder,
             download_dir=options.wheel_dir,
             use_user_site=False,
+            verbosity=self.verbosity,
         )
 
         resolver = self.make_resolver(
diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py
index 8a1799689..952e063d7 100644
--- a/src/pip/_internal/exceptions.py
+++ b/src/pip/_internal/exceptions.py
@@ -3,9 +3,12 @@
 import configparser
 import re
 from itertools import chain, groupby, repeat
-from typing import TYPE_CHECKING, Dict, Iterator, List, Optional
+from typing import TYPE_CHECKING, Dict, List, Optional, Union
 
 from pip._vendor.requests.models import Request, Response
+from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult
+from pip._vendor.rich.markup import escape
+from pip._vendor.rich.text import Text
 
 if TYPE_CHECKING:
     from hashlib import _Hash
@@ -22,13 +25,21 @@ def _is_kebab_case(s: str) -> bool:
     return re.match(r"^[a-z]+(-[a-z]+)*$", s) is not None
 
 
-def _prefix_with_indent(prefix: str, s: str, indent: Optional[str] = None) -> str:
-    if indent is None:
-        indent = " " * len(prefix)
+def _prefix_with_indent(
+    s: Union[Text, str],
+    console: Console,
+    *,
+    prefix: str,
+    indent: str,
+) -> Text:
+    if isinstance(s, Text):
+        text = s
     else:
-        assert len(indent) == len(prefix)
-    message = s.replace("\n", "\n" + indent)
-    return f"{prefix}{message}\n"
+        text = console.render_str(s)
+
+    return console.render_str(prefix, overflow="ignore") + console.render_str(
+        f"\n{indent}", overflow="ignore"
+    ).join(text.split(allow_blank=True))
 
 
 class PipError(Exception):
@@ -36,12 +47,14 @@ class PipError(Exception):
 
 
 class DiagnosticPipError(PipError):
-    """A pip error, that presents diagnostic information to the user.
+    """An error, that presents diagnostic information to the user.
 
     This contains a bunch of logic, to enable pretty presentation of our error
     messages. Each error gets a unique reference. Each error can also include
     additional context, a hint and/or a note -- which are presented with the
     main error message in a consistent style.
+
+    This is adapted from the error output styling in `sphinx-theme-builder`.
     """
 
     reference: str
@@ -49,48 +62,103 @@ class DiagnosticPipError(PipError):
     def __init__(
         self,
         *,
-        message: str,
-        context: Optional[str],
-        hint_stmt: Optional[str],
-        attention_stmt: Optional[str] = None,
-        reference: Optional[str] = None,
         kind: 'Literal["error", "warning"]' = "error",
+        reference: Optional[str] = None,
+        message: Union[str, Text],
+        context: Optional[Union[str, Text]],
+        hint_stmt: Optional[Union[str, Text]],
+        note_stmt: Optional[Union[str, Text]] = None,
+        link: Optional[str] = None,
     ) -> None:
-
         # Ensure a proper reference is provided.
         if reference is None:
             assert hasattr(self, "reference"), "error reference not provided!"
             reference = self.reference
         assert _is_kebab_case(reference), "error reference must be kebab-case!"
 
-        super().__init__(f"{reference}: {message}")
-
         self.kind = kind
+        self.reference = reference
+
         self.message = message
         self.context = context
 
-        self.reference = reference
-        self.attention_stmt = attention_stmt
+        self.note_stmt = note_stmt
         self.hint_stmt = hint_stmt
 
-    def __str__(self) -> str:
-        return "".join(self._string_parts())
-
-    def _string_parts(self) -> Iterator[str]:
-        # Present the main message, with relevant context indented.
-        yield f"{self.message}\n"
-        if self.context is not None:
-            yield f"\n{self.context}\n"
+        self.link = link
 
-        # Space out the note/hint messages.
-        if self.attention_stmt is not None or self.hint_stmt is not None:
-            yield "\n"
+        super().__init__(f"<{self.__class__.__name__}: {self.reference}>")
 
-        if self.attention_stmt is not None:
-            yield _prefix_with_indent("Note: ", self.attention_stmt)
+    def __repr__(self) -> str:
+        return (
+            f"<{self.__class__.__name__}("
+            f"reference={self.reference!r}, "
+            f"message={self.message!r}, "
+            f"context={self.context!r}, "
+            f"note_stmt={self.note_stmt!r}, "
+            f"hint_stmt={self.hint_stmt!r}"
+            ")>"
+        )
 
+    def __rich_console__(
+        self,
+        console: Console,
+        options: ConsoleOptions,
+    ) -> RenderResult:
+        colour = "red" if self.kind == "error" else "yellow"
+
+        yield f"[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]"
+        yield ""
+
+        if not options.ascii_only:
+            # Present the main message, with relevant context indented.
+            if self.context is not None:
+                yield _prefix_with_indent(
+                    self.message,
+                    console,
+                    prefix=f"[{colour}]×[/] ",
+                    indent=f"[{colour}]│[/] ",
+                )
+                yield _prefix_with_indent(
+                    self.context,
+                    console,
+                    prefix=f"[{colour}]╰─>[/] ",
+                    indent=f"[{colour}]   [/] ",
+                )
+            else:
+                yield _prefix_with_indent(
+                    self.message,
+                    console,
+                    prefix="[red]×[/] ",
+                    indent="  ",
+                )
+        else:
+            yield self.message
+            if self.context is not None:
+                yield ""
+                yield self.context
+
+        if self.note_stmt is not None or self.hint_stmt is not None:
+            yield ""
+
+        if self.note_stmt is not None:
+            yield _prefix_with_indent(
+                self.note_stmt,
+                console,
+                prefix="[magenta bold]note[/]: ",
+                indent="      ",
+            )
         if self.hint_stmt is not None:
-            yield _prefix_with_indent("Hint: ", self.hint_stmt)
+            yield _prefix_with_indent(
+                self.hint_stmt,
+                console,
+                prefix="[cyan bold]hint[/]: ",
+                indent="      ",
+            )
+
+        if self.link is not None:
+            yield ""
+            yield f"Link: {self.link}"
 
 
 #
@@ -115,15 +183,13 @@ class MissingPyProjectBuildRequires(DiagnosticPipError):
 
     def __init__(self, *, package: str) -> None:
         super().__init__(
-            message=f"Can not process {package}",
-            context=(
+            message=f"Can not process {escape(package)}",
+            context=Text(
                 "This package has an invalid pyproject.toml file.\n"
                 "The [build-system] table is missing the mandatory `requires` key."
             ),
-            attention_stmt=(
-                "This is an issue with the package mentioned above, not pip."
-            ),
-            hint_stmt="See PEP 518 for the detailed specification.",
+            note_stmt="This is an issue with the package mentioned above, not pip.",
+            hint_stmt=Text("See PEP 518 for the detailed specification."),
         )
 
 
@@ -134,16 +200,13 @@ class InvalidPyProjectBuildRequires(DiagnosticPipError):
 
     def __init__(self, *, package: str, reason: str) -> None:
         super().__init__(
-            message=f"Can not process {package}",
-            context=(
+            message=f"Can not process {escape(package)}",
+            context=Text(
                 "This package has an invalid `build-system.requires` key in "
-                "pyproject.toml.\n"
-                f"{reason}"
-            ),
-            hint_stmt="See PEP 518 for the detailed specification.",
-            attention_stmt=(
-                "This is an issue with the package mentioned above, not pip."
+                f"pyproject.toml.\n{reason}"
             ),
+            note_stmt="This is an issue with the package mentioned above, not pip.",
+            hint_stmt=Text("See PEP 518 for the detailed specification."),
         )
 
 
diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py
index 82449b488..86b7b65dd 100644
--- a/src/pip/_internal/index/package_finder.py
+++ b/src/pip/_internal/index/package_finder.py
@@ -37,7 +37,6 @@ from pip._internal.utils.logging import indent_log
 from pip._internal.utils.misc import build_netloc
 from pip._internal.utils.packaging import check_requires_python
 from pip._internal.utils.unpacking import SUPPORTED_EXTENSIONS
-from pip._internal.utils.urls import url_to_path
 
 __all__ = ["FormatControl", "BestCandidateResult", "PackageFinder"]
 
@@ -816,7 +815,14 @@ class PackageFinder:
         )
 
         if logger.isEnabledFor(logging.DEBUG) and file_candidates:
-            paths = [url_to_path(c.link.url) for c in file_candidates]
+            paths = []
+            for candidate in file_candidates:
+                assert candidate.link.url  # we need to have a URL
+                try:
+                    paths.append(candidate.link.file_path)
+                except Exception:
+                    paths.append(candidate.link.url)  # it's not a local file
+
             logger.debug("Local files found: %s", ", ".join(paths))
 
         # This is an intentional priority ordering
diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py
index 34cf9a51b..a726f031a 100644
--- a/src/pip/_internal/operations/prepare.py
+++ b/src/pip/_internal/operations/prepare.py
@@ -59,10 +59,10 @@ def _get_prepared_distribution(
     return abstract_dist.get_metadata_distribution()
 
 
-def unpack_vcs_link(link: Link, location: str) -> None:
+def unpack_vcs_link(link: Link, location: str, verbosity: int) -> None:
     vcs_backend = vcs.get_backend_for_scheme(link.scheme)
     assert vcs_backend is not None
-    vcs_backend.unpack(location, url=hide_url(link.url))
+    vcs_backend.unpack(location, url=hide_url(link.url), verbosity=verbosity)
 
 
 class File:
@@ -175,6 +175,7 @@ def unpack_url(
     link: Link,
     location: str,
     download: Downloader,
+    verbosity: int,
     download_dir: Optional[str] = None,
     hashes: Optional[Hashes] = None,
 ) -> Optional[File]:
@@ -187,7 +188,7 @@ def unpack_url(
     """
     # non-editable vcs urls
     if link.is_vcs:
-        unpack_vcs_link(link, location)
+        unpack_vcs_link(link, location, verbosity=verbosity)
         return None
 
     # Once out-of-tree-builds are no longer supported, could potentially
@@ -267,6 +268,7 @@ class RequirementPreparer:
         require_hashes: bool,
         use_user_site: bool,
         lazy_wheel: bool,
+        verbosity: int,
         in_tree_build: bool,
     ) -> None:
         super().__init__()
@@ -295,6 +297,9 @@ class RequirementPreparer:
         # Should wheels be downloaded lazily?
         self.use_lazy_wheel = lazy_wheel
 
+        # How verbose should underlying tooling be?
+        self.verbosity = verbosity
+
         # Should in-tree builds be used for local paths?
         self.in_tree_build = in_tree_build
 
@@ -525,7 +530,12 @@ class RequirementPreparer:
         elif link.url not in self._downloaded:
             try:
                 local_file = unpack_url(
-                    link, req.source_dir, self._download, self.download_dir, hashes
+                    link,
+                    req.source_dir,
+                    self._download,
+                    self.verbosity,
+                    self.download_dir,
+                    hashes,
                 )
             except NetworkConnectionError as exc:
                 raise InstallationError(
diff --git a/src/pip/_internal/pyproject.py b/src/pip/_internal/pyproject.py
index 0607b048b..e183eaf86 100644
--- a/src/pip/_internal/pyproject.py
+++ b/src/pip/_internal/pyproject.py
@@ -60,7 +60,7 @@ def load_pyproject_toml(
 
     if has_pyproject:
         with open(pyproject_toml, encoding="utf-8") as f:
-            pp_toml = tomli.load(f)
+            pp_toml = tomli.loads(f.read())
         build_system = pp_toml.get("build-system")
     else:
         build_system = None
diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py
index 6fa6eb2a2..bd8da9ebd 100644
--- a/src/pip/_internal/req/req_install.py
+++ b/src/pip/_internal/req/req_install.py
@@ -607,7 +607,7 @@ class InstallRequirement:
         # So here, if it's neither a path nor a valid VCS URL, it's a bug.
         assert vcs_backend, f"Unsupported VCS URL {self.link.url}"
         hidden_url = hide_url(self.link.url)
-        vcs_backend.obtain(self.source_dir, url=hidden_url)
+        vcs_backend.obtain(self.source_dir, url=hidden_url, verbosity=0)
 
     # Top-level Actions
     def uninstall(
diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py
index c3aaa6957..a54dfd325 100644
--- a/src/pip/_internal/resolution/resolvelib/factory.py
+++ b/src/pip/_internal/resolution/resolvelib/factory.py
@@ -273,14 +273,27 @@ class Factory:
             )
             icans = list(result.iter_applicable())
 
-            # PEP 592: Yanked releases must be ignored unless only yanked
-            # releases can satisfy the version range. So if this is false,
-            # all yanked icans need to be skipped.
+            # PEP 592: Yanked releases are ignored unless the specifier
+            # explicitely pins a version (via '==' or '===') that can be
+            # solely satisfied by a yanked release.
             all_yanked = all(ican.link.is_yanked for ican in icans)
 
+            def is_pinned(specifier: SpecifierSet) -> bool:
+                for sp in specifier:
+                    if sp.operator == "===":
+                        return True
+                    if sp.operator != "==":
+                        continue
+                    if sp.version.endswith(".*"):
+                        continue
+                    return True
+                return False
+
+            pinned = is_pinned(specifier)
+
             # PackageFinder returns earlier versions first, so we reverse.
             for ican in reversed(icans):
-                if not all_yanked and ican.link.is_yanked:
+                if not (all_yanked and pinned) and ican.link.is_yanked:
                     continue
                 func = functools.partial(
                     self._make_candidate_from_link,
diff --git a/src/pip/_internal/self_outdated_check.py b/src/pip/_internal/self_outdated_check.py
index 72f70fc25..19a5f280f 100644
--- a/src/pip/_internal/self_outdated_check.py
+++ b/src/pip/_internal/self_outdated_check.py
@@ -165,7 +165,11 @@ def pip_self_version_check(session: PipSession, options: optparse.Values) -> Non
         # We cannot tell how the current pip is available in the current
         # command context, so be pragmatic here and suggest the command
         # that's always available. This does not accommodate spaces in
-        # `sys.executable`.
+        # `sys.executable` on purpose as it is not possible to do it
+        # correctly without knowing the user's shell. Thus,
+        # it won't be done until possible through the standard library.
+        # Do not be tempted to use the undocumented subprocess.list2cmdline.
+        # It is considered an internal implementation detail for a reason.
         pip_cmd = f"{sys.executable} -m pip"
         logger.warning(
             "You are using pip version %s; however, version %s is "
diff --git a/src/pip/_internal/utils/logging.py b/src/pip/_internal/utils/logging.py
index a4b828a84..1c0cd8e26 100644
--- a/src/pip/_internal/utils/logging.py
+++ b/src/pip/_internal/utils/logging.py
@@ -4,28 +4,28 @@ import logging
 import logging.handlers
 import os
 import sys
+import threading
+from dataclasses import dataclass
 from logging import Filter
-from typing import IO, Any, Callable, Iterator, Optional, TextIO, Type, cast
-
+from typing import IO, Any, ClassVar, Iterator, List, Optional, TextIO, Type
+
+from pip._vendor.rich.console import (
+    Console,
+    ConsoleOptions,
+    ConsoleRenderable,
+    RenderResult,
+)
+from pip._vendor.rich.highlighter import NullHighlighter
+from pip._vendor.rich.logging import RichHandler
+from pip._vendor.rich.segment import Segment
+from pip._vendor.rich.style import Style
+
+from pip._internal.exceptions import DiagnosticPipError
 from pip._internal.utils._log import VERBOSE, getLogger
 from pip._internal.utils.compat import WINDOWS
 from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX
 from pip._internal.utils.misc import ensure_dir
 
-try:
-    import threading
-except ImportError:
-    import dummy_threading as threading  # type: ignore
-
-
-try:
-    from pip._vendor import colorama
-# Lots of different errors can come from this, including SystemError and
-# ImportError.
-except Exception:
-    colorama = None
-
-
 _log_state = threading.local()
 subprocess_logger = getLogger("pip.subprocessor")
 
@@ -119,78 +119,63 @@ class IndentingFormatter(logging.Formatter):
         return formatted
 
 
-def _color_wrap(*colors: str) -> Callable[[str], str]:
-    def wrapped(inp: str) -> str:
-        return "".join(list(colors) + [inp, colorama.Style.RESET_ALL])
-
-    return wrapped
-
-
-class ColorizedStreamHandler(logging.StreamHandler):
-
-    # Don't build up a list of colors if we don't have colorama
-    if colorama:
-        COLORS = [
-            # This needs to be in order from highest logging level to lowest.
-            (logging.ERROR, _color_wrap(colorama.Fore.RED)),
-            (logging.WARNING, _color_wrap(colorama.Fore.YELLOW)),
-        ]
-    else:
-        COLORS = []
-
-    def __init__(self, stream: Optional[TextIO] = None, no_color: bool = None) -> None:
-        super().__init__(stream)
-        self._no_color = no_color
-
-        if WINDOWS and colorama:
-            self.stream = colorama.AnsiToWin32(self.stream)
-
-    def _using_stdout(self) -> bool:
-        """
-        Return whether the handler is using sys.stdout.
-        """
-        if WINDOWS and colorama:
-            # Then self.stream is an AnsiToWin32 object.
-            stream = cast(colorama.AnsiToWin32, self.stream)
-            return stream.wrapped is sys.stdout
-
-        return self.stream is sys.stdout
-
-    def should_color(self) -> bool:
-        # Don't colorize things if we do not have colorama or if told not to
-        if not colorama or self._no_color:
-            return False
-
-        real_stream = (
-            self.stream
-            if not isinstance(self.stream, colorama.AnsiToWin32)
-            else self.stream.wrapped
+@dataclass
+class IndentedRenderable:
+    renderable: ConsoleRenderable
+    indent: int
+
+    def __rich_console__(
+        self, console: Console, options: ConsoleOptions
+    ) -> RenderResult:
+        segments = console.render(self.renderable, options)
+        lines = Segment.split_lines(segments)
+        for line in lines:
+            yield Segment(" " * self.indent)
+            yield from line
+            yield Segment("\n")
+
+
+class RichPipStreamHandler(RichHandler):
+    KEYWORDS: ClassVar[Optional[List[str]]] = []
+
+    def __init__(self, stream: Optional[TextIO], no_color: bool) -> None:
+        super().__init__(
+            console=Console(file=stream, no_color=no_color, soft_wrap=True),
+            show_time=False,
+            show_level=False,
+            show_path=False,
+            highlighter=NullHighlighter(),
         )
 
-        # If the stream is a tty we should color it
-        if hasattr(real_stream, "isatty") and real_stream.isatty():
-            return True
-
-        # If we have an ANSI term we should color it
-        if os.environ.get("TERM") == "ANSI":
-            return True
-
-        # If anything else we should not color it
-        return False
-
-    def format(self, record: logging.LogRecord) -> str:
-        msg = super().format(record)
-
-        if self.should_color():
-            for level, color in self.COLORS:
-                if record.levelno >= level:
-                    msg = color(msg)
-                    break
-
-        return msg
+    # Our custom override on Rich's logger, to make things work as we need them to.
+    def emit(self, record: logging.LogRecord) -> None:
+        style: Optional[Style] = None
+
+        # If we are given a diagnostic error to present, present it with indentation.
+        if record.msg == "[present-diagnostic]" and len(record.args) == 1:
+            diagnostic_error: DiagnosticPipError = record.args[0]  # type: ignore[index]
+            assert isinstance(diagnostic_error, DiagnosticPipError)
+
+            renderable: ConsoleRenderable = IndentedRenderable(
+                diagnostic_error, indent=get_indentation()
+            )
+        else:
+            message = self.format(record)
+            renderable = self.render_message(record, message)
+            if record.levelno is not None:
+                if record.levelno >= logging.ERROR:
+                    style = Style(color="red")
+                elif record.levelno >= logging.WARNING:
+                    style = Style(color="yellow")
+
+        try:
+            self.console.print(renderable, overflow="ignore", crop=False, style=style)
+        except Exception:
+            self.handleError(record)
 
-    # The logging module says handleError() can be customized.
     def handleError(self, record: logging.LogRecord) -> None:
+        """Called when logging is unable to log some output."""
+
         exc_class, exc = sys.exc_info()[:2]
         # If a broken pipe occurred while calling write() or flush() on the
         # stdout stream in logging's Handler.emit(), then raise our special
@@ -199,7 +184,7 @@ class ColorizedStreamHandler(logging.StreamHandler):
         if (
             exc_class
             and exc
-            and self._using_stdout()
+            and self.console.file is sys.stdout
             and _is_broken_pipe_error(exc_class, exc)
         ):
             raise BrokenStdoutLoggingError()
@@ -275,7 +260,7 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str])
         "stderr": "ext://sys.stderr",
     }
     handler_classes = {
-        "stream": "pip._internal.utils.logging.ColorizedStreamHandler",
+        "stream": "pip._internal.utils.logging.RichPipStreamHandler",
         "file": "pip._internal.utils.logging.BetterRotatingFileHandler",
     }
     handlers = ["console", "console_errors", "console_subprocess"] + (
@@ -333,8 +318,8 @@ def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str])
                 "console_subprocess": {
                     "level": level,
                     "class": handler_classes["stream"],
-                    "no_color": no_color,
                     "stream": log_streams["stderr"],
+                    "no_color": no_color,
                     "filters": ["restrict_to_subprocess"],
                     "formatter": "indent",
                 },
diff --git a/src/pip/_internal/vcs/bazaar.py b/src/pip/_internal/vcs/bazaar.py
index 82e75954a..a7b16e2e0 100644
--- a/src/pip/_internal/vcs/bazaar.py
+++ b/src/pip/_internal/vcs/bazaar.py
@@ -33,7 +33,9 @@ class Bazaar(VersionControl):
     def get_base_rev_args(rev: str) -> List[str]:
         return ["-r", rev]
 
-    def fetch_new(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+    def fetch_new(
+        self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+    ) -> None:
         rev_display = rev_options.to_display()
         logger.info(
             "Checking out %s%s to %s",
@@ -41,7 +43,13 @@ class Bazaar(VersionControl):
             rev_display,
             display_path(dest),
         )
-        cmd_args = make_command("branch", "-q", rev_options.to_args(), url, dest)
+        if verbosity <= 0:
+            flag = "--quiet"
+        elif verbosity == 1:
+            flag = ""
+        else:
+            flag = f"-{'v'*verbosity}"
+        cmd_args = make_command("branch", flag, rev_options.to_args(), url, dest)
         self.run_command(cmd_args)
 
     def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
diff --git a/src/pip/_internal/vcs/git.py b/src/pip/_internal/vcs/git.py
index 7a78ad12d..93be90472 100644
--- a/src/pip/_internal/vcs/git.py
+++ b/src/pip/_internal/vcs/git.py
@@ -253,9 +253,17 @@ class Git(VersionControl):
 
         return cls.get_revision(dest) == name
 
-    def fetch_new(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+    def fetch_new(
+        self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+    ) -> None:
         rev_display = rev_options.to_display()
         logger.info("Cloning %s%s to %s", url, rev_display, display_path(dest))
+        if verbosity <= 0:
+            flags: Tuple[str, ...] = ("--quiet",)
+        elif verbosity == 1:
+            flags = ()
+        else:
+            flags = ("--verbose", "--progress")
         if self.get_git_version() >= (2, 17):
             # Git added support for partial clone in 2.17
             # https://git-scm.com/docs/partial-clone
@@ -264,13 +272,13 @@ class Git(VersionControl):
                 make_command(
                     "clone",
                     "--filter=blob:none",
-                    "-q",
+                    *flags,
                     url,
                     dest,
                 )
             )
         else:
-            self.run_command(make_command("clone", "-q", url, dest))
+            self.run_command(make_command("clone", *flags, url, dest))
 
         if rev_options.rev:
             # Then a specific revision was requested.
diff --git a/src/pip/_internal/vcs/mercurial.py b/src/pip/_internal/vcs/mercurial.py
index 410c79d90..2a005e0af 100644
--- a/src/pip/_internal/vcs/mercurial.py
+++ b/src/pip/_internal/vcs/mercurial.py
@@ -1,7 +1,7 @@
 import configparser
 import logging
 import os
-from typing import List, Optional
+from typing import List, Optional, Tuple
 
 from pip._internal.exceptions import BadCommand, InstallationError
 from pip._internal.utils.misc import HiddenText, display_path
@@ -33,7 +33,9 @@ class Mercurial(VersionControl):
     def get_base_rev_args(rev: str) -> List[str]:
         return [rev]
 
-    def fetch_new(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+    def fetch_new(
+        self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+    ) -> None:
         rev_display = rev_options.to_display()
         logger.info(
             "Cloning hg %s%s to %s",
@@ -41,9 +43,17 @@ class Mercurial(VersionControl):
             rev_display,
             display_path(dest),
         )
-        self.run_command(make_command("clone", "--noupdate", "-q", url, dest))
+        if verbosity <= 0:
+            flags: Tuple[str, ...] = ("--quiet",)
+        elif verbosity == 1:
+            flags = ()
+        elif verbosity == 2:
+            flags = ("--verbose",)
+        else:
+            flags = ("--verbose", "--debug")
+        self.run_command(make_command("clone", "--noupdate", *flags, url, dest))
         self.run_command(
-            make_command("update", "-q", rev_options.to_args()),
+            make_command("update", *flags, rev_options.to_args()),
             cwd=dest,
         )
 
diff --git a/src/pip/_internal/vcs/subversion.py b/src/pip/_internal/vcs/subversion.py
index b5b6fd5c0..89c8754ce 100644
--- a/src/pip/_internal/vcs/subversion.py
+++ b/src/pip/_internal/vcs/subversion.py
@@ -277,7 +277,9 @@ class Subversion(VersionControl):
 
         return []
 
-    def fetch_new(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+    def fetch_new(
+        self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+    ) -> None:
         rev_display = rev_options.to_display()
         logger.info(
             "Checking out %s%s to %s",
@@ -285,9 +287,13 @@ class Subversion(VersionControl):
             rev_display,
             display_path(dest),
         )
+        if verbosity <= 0:
+            flag = "--quiet"
+        else:
+            flag = ""
         cmd_args = make_command(
             "checkout",
-            "-q",
+            flag,
             self.get_remote_call_options(),
             rev_options.to_args(),
             url,
diff --git a/src/pip/_internal/vcs/versioncontrol.py b/src/pip/_internal/vcs/versioncontrol.py
index 1139051f3..7b93657b9 100644
--- a/src/pip/_internal/vcs/versioncontrol.py
+++ b/src/pip/_internal/vcs/versioncontrol.py
@@ -458,7 +458,9 @@ class VersionControl:
         """
         return cls.normalize_url(url1) == cls.normalize_url(url2)
 
-    def fetch_new(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+    def fetch_new(
+        self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+    ) -> None:
         """
         Fetch a revision from a repository, in the case that this is the
         first fetch from the repository.
@@ -466,6 +468,7 @@ class VersionControl:
         Args:
           dest: the directory to fetch the repository to.
           rev_options: a RevOptions object.
+          verbosity: verbosity level.
         """
         raise NotImplementedError
 
@@ -498,18 +501,19 @@ class VersionControl:
         """
         raise NotImplementedError
 
-    def obtain(self, dest: str, url: HiddenText) -> None:
+    def obtain(self, dest: str, url: HiddenText, verbosity: int) -> None:
         """
         Install or update in editable mode the package represented by this
         VersionControl object.
 
         :param dest: the repository directory in which to install or update.
         :param url: the repository URL starting with a vcs prefix.
+        :param verbosity: verbosity level.
         """
         url, rev_options = self.get_url_rev_options(url)
 
         if not os.path.exists(dest):
-            self.fetch_new(dest, url, rev_options)
+            self.fetch_new(dest, url, rev_options, verbosity=verbosity)
             return
 
         rev_display = rev_options.to_display()
@@ -565,14 +569,14 @@ class VersionControl:
         if response == "w":
             logger.warning("Deleting %s", display_path(dest))
             rmtree(dest)
-            self.fetch_new(dest, url, rev_options)
+            self.fetch_new(dest, url, rev_options, verbosity=verbosity)
             return
 
         if response == "b":
             dest_dir = backup_dir(dest)
             logger.warning("Backing up %s to %s", display_path(dest), dest_dir)
             shutil.move(dest, dest_dir)
-            self.fetch_new(dest, url, rev_options)
+            self.fetch_new(dest, url, rev_options, verbosity=verbosity)
             return
 
         # Do nothing if the response is "i".
@@ -586,16 +590,17 @@ class VersionControl:
             )
             self.switch(dest, url, rev_options)
 
-    def unpack(self, location: str, url: HiddenText) -> None:
+    def unpack(self, location: str, url: HiddenText, verbosity: int) -> None:
         """
         Clean up current location and download the url repository
         (and vcs infos) into location
 
         :param url: the repository URL starting with a vcs prefix.
+        :param verbosity: verbosity level.
         """
         if os.path.exists(location):
             rmtree(location)
-        self.obtain(location, url=url)
+        self.obtain(location, url=url, verbosity=verbosity)
 
     @classmethod
     def get_remote_url(cls, location: str) -> str:
diff --git a/src/pip/_vendor/requests/__init__.py b/src/pip/_vendor/requests/__init__.py
index 4f80e28fc..75a633bf9 100644
--- a/src/pip/_vendor/requests/__init__.py
+++ b/src/pip/_vendor/requests/__init__.py
@@ -141,7 +141,7 @@ from .status_codes import codes
 from .exceptions import (
     RequestException, Timeout, URLRequired,
     TooManyRedirects, HTTPError, ConnectionError,
-    FileModeWarning, ConnectTimeout, ReadTimeout
+    FileModeWarning, ConnectTimeout, ReadTimeout, JSONDecodeError
 )
 
 # Set default logging handler to avoid "No handler found" warnings.
diff --git a/src/pip/_vendor/requests/__version__.py b/src/pip/_vendor/requests/__version__.py
index 0d7cde1df..e973b03b5 100644
--- a/src/pip/_vendor/requests/__version__.py
+++ b/src/pip/_vendor/requests/__version__.py
@@ -5,10 +5,10 @@
 __title__ = 'requests'
 __description__ = 'Python HTTP for Humans.'
 __url__ = 'https://requests.readthedocs.io'
-__version__ = '2.26.0'
-__build__ = 0x022600
+__version__ = '2.27.1'
+__build__ = 0x022701
 __author__ = 'Kenneth Reitz'
 __author_email__ = 'me@kennethreitz.org'
 __license__ = 'Apache 2.0'
-__copyright__ = 'Copyright 2020 Kenneth Reitz'
+__copyright__ = 'Copyright 2022 Kenneth Reitz'
 __cake__ = u'\u2728 \U0001f370 \u2728'
diff --git a/src/pip/_vendor/requests/adapters.py b/src/pip/_vendor/requests/adapters.py
index c30e7c92d..b3dfa5706 100644
--- a/src/pip/_vendor/requests/adapters.py
+++ b/src/pip/_vendor/requests/adapters.py
@@ -19,6 +19,7 @@ from pip._vendor.urllib3.util.retry import Retry
 from pip._vendor.urllib3.exceptions import ClosedPoolError
 from pip._vendor.urllib3.exceptions import ConnectTimeoutError
 from pip._vendor.urllib3.exceptions import HTTPError as _HTTPError
+from pip._vendor.urllib3.exceptions import InvalidHeader as _InvalidHeader
 from pip._vendor.urllib3.exceptions import MaxRetryError
 from pip._vendor.urllib3.exceptions import NewConnectionError
 from pip._vendor.urllib3.exceptions import ProxyError as _ProxyError
@@ -37,7 +38,7 @@ from .structures import CaseInsensitiveDict
 from .cookies import extract_cookies_to_jar
 from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError,
                          ProxyError, RetryError, InvalidSchema, InvalidProxyURL,
-                         InvalidURL)
+                         InvalidURL, InvalidHeader)
 from .auth import _basic_auth_str
 
 try:
@@ -457,9 +458,11 @@ class HTTPAdapter(BaseAdapter):
                 low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)
 
                 try:
+                    skip_host = 'Host' in request.headers
                     low_conn.putrequest(request.method,
                                         url,
-                                        skip_accept_encoding=True)
+                                        skip_accept_encoding=True,
+                                        skip_host=skip_host)
 
                     for header, value in request.headers.items():
                         low_conn.putheader(header, value)
@@ -527,6 +530,8 @@ class HTTPAdapter(BaseAdapter):
                 raise SSLError(e, request=request)
             elif isinstance(e, ReadTimeoutError):
                 raise ReadTimeout(e, request=request)
+            elif isinstance(e, _InvalidHeader):
+                raise InvalidHeader(e, request=request)
             else:
                 raise
 
diff --git a/src/pip/_vendor/requests/compat.py b/src/pip/_vendor/requests/compat.py
index 9e2937167..f98cc910f 100644
--- a/src/pip/_vendor/requests/compat.py
+++ b/src/pip/_vendor/requests/compat.py
@@ -50,13 +50,13 @@ if is_py2:
     # Keep OrderedDict for backwards compatibility.
     from collections import Callable, Mapping, MutableMapping, OrderedDict
 
-
     builtin_str = str
     bytes = str
     str = unicode
     basestring = basestring
     numeric_types = (int, long, float)
     integer_types = (int, long)
+    JSONDecodeError = ValueError
 
 elif is_py3:
     from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
@@ -67,6 +67,7 @@ elif is_py3:
     # Keep OrderedDict for backwards compatibility.
     from collections import OrderedDict
     from collections.abc import Callable, Mapping, MutableMapping
+    from json import JSONDecodeError
 
     builtin_str = str
     str = str
diff --git a/src/pip/_vendor/requests/exceptions.py b/src/pip/_vendor/requests/exceptions.py
index 9f0ad778b..83b9232e4 100644
--- a/src/pip/_vendor/requests/exceptions.py
+++ b/src/pip/_vendor/requests/exceptions.py
@@ -8,6 +8,8 @@ This module contains the set of Requests' exceptions.
 """
 from pip._vendor.urllib3.exceptions import HTTPError as BaseHTTPError
 
+from .compat import JSONDecodeError as CompatJSONDecodeError
+
 
 class RequestException(IOError):
     """There was an ambiguous exception that occurred while handling your
@@ -29,6 +31,10 @@ class InvalidJSONError(RequestException):
     """A JSON error occurred."""
 
 
+class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError):
+    """Couldn't decode the text into json"""
+
+
 class HTTPError(RequestException):
     """An HTTP error occurred."""
 
@@ -74,11 +80,11 @@ class TooManyRedirects(RequestException):
 
 
 class MissingSchema(RequestException, ValueError):
-    """The URL schema (e.g. http or https) is missing."""
+    """The URL scheme (e.g. http or https) is missing."""
 
 
 class InvalidSchema(RequestException, ValueError):
-    """See defaults.py for valid schemas."""
+    """The URL scheme provided is either invalid or unsupported."""
 
 
 class InvalidURL(RequestException, ValueError):
diff --git a/src/pip/_vendor/requests/models.py b/src/pip/_vendor/requests/models.py
index c10c6011b..f538c1054 100644
--- a/src/pip/_vendor/requests/models.py
+++ b/src/pip/_vendor/requests/models.py
@@ -29,7 +29,9 @@ from .auth import HTTPBasicAuth
 from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar
 from .exceptions import (
     HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError,
-    ContentDecodingError, ConnectionError, StreamConsumedError, InvalidJSONError)
+    ContentDecodingError, ConnectionError, StreamConsumedError,
+    InvalidJSONError)
+from .exceptions import JSONDecodeError as RequestsJSONDecodeError
 from ._internal_utils import to_native_string, unicode_is_ascii
 from .utils import (
     guess_filename, get_auth_from_url, requote_uri,
@@ -38,7 +40,7 @@ from .utils import (
 from .compat import (
     Callable, Mapping,
     cookielib, urlunparse, urlsplit, urlencode, str, bytes,
-    is_py2, chardet, builtin_str, basestring)
+    is_py2, chardet, builtin_str, basestring, JSONDecodeError)
 from .compat import json as complexjson
 from .status_codes import codes
 
@@ -384,7 +386,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
             raise InvalidURL(*e.args)
 
         if not scheme:
-            error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?")
+            error = ("Invalid URL {0!r}: No scheme supplied. Perhaps you meant http://{0}?")
             error = error.format(to_native_string(url, 'utf8'))
 
             raise MissingSchema(error)
@@ -401,7 +403,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
                 host = self._get_idna_encoded_host(host)
             except UnicodeError:
                 raise InvalidURL('URL has an invalid label.')
-        elif host.startswith(u'*'):
+        elif host.startswith((u'*', u'.')):
             raise InvalidURL('URL has an invalid label.')
 
         # Carefully reconstruct the network location
@@ -468,9 +470,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
             content_type = 'application/json'
 
             try:
-              body = complexjson.dumps(json, allow_nan=False)
+                body = complexjson.dumps(json, allow_nan=False)
             except ValueError as ve:
-              raise InvalidJSONError(ve, request=self)
+                raise InvalidJSONError(ve, request=self)
 
             if not isinstance(body, bytes):
                 body = body.encode('utf-8')
@@ -882,12 +884,8 @@ class Response(object):
         r"""Returns the json-encoded content of a response, if any.
 
         :param \*\*kwargs: Optional arguments that ``json.loads`` takes.
-        :raises simplejson.JSONDecodeError: If the response body does not
-            contain valid json and simplejson is installed.
-        :raises json.JSONDecodeError: If the response body does not contain
-            valid json and simplejson is not installed on Python 3.
-        :raises ValueError: If the response body does not contain valid
-            json and simplejson is not installed on Python 2.        
+        :raises requests.exceptions.JSONDecodeError: If the response body does not
+            contain valid json.
         """
 
         if not self.encoding and self.content and len(self.content) > 3:
@@ -907,7 +905,16 @@ class Response(object):
                     # and the server didn't bother to tell us what codec *was*
                     # used.
                     pass
-        return complexjson.loads(self.text, **kwargs)
+
+        try:
+            return complexjson.loads(self.text, **kwargs)
+        except JSONDecodeError as e:
+            # Catch JSON-related errors and raise as requests.JSONDecodeError
+            # This aliases json.JSONDecodeError and simplejson.JSONDecodeError
+            if is_py2: # e is a ValueError
+                raise RequestsJSONDecodeError(e.message)
+            else:
+                raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
 
     @property
     def links(self):
diff --git a/src/pip/_vendor/requests/sessions.py b/src/pip/_vendor/requests/sessions.py
index ae4bcc8e7..3f59cab92 100644
--- a/src/pip/_vendor/requests/sessions.py
+++ b/src/pip/_vendor/requests/sessions.py
@@ -29,7 +29,7 @@ from .adapters import HTTPAdapter
 
 from .utils import (
     requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies,
-    get_auth_from_url, rewind_body
+    get_auth_from_url, rewind_body, resolve_proxies
 )
 
 from .status_codes import codes
@@ -269,7 +269,6 @@ class SessionRedirectMixin(object):
         if new_auth is not None:
             prepared_request.prepare_auth(new_auth)
 
-
     def rebuild_proxies(self, prepared_request, proxies):
         """This method re-evaluates the proxy configuration by considering the
         environment variables. If we are redirected to a URL covered by
@@ -282,21 +281,9 @@ class SessionRedirectMixin(object):
 
         :rtype: dict
         """
-        proxies = proxies if proxies is not None else {}
         headers = prepared_request.headers
-        url = prepared_request.url
-        scheme = urlparse(url).scheme
-        new_proxies = proxies.copy()
-        no_proxy = proxies.get('no_proxy')
-
-        bypass_proxy = should_bypass_proxies(url, no_proxy=no_proxy)
-        if self.trust_env and not bypass_proxy:
-            environ_proxies = get_environ_proxies(url, no_proxy=no_proxy)
-
-            proxy = environ_proxies.get(scheme, environ_proxies.get('all'))
-
-            if proxy:
-                new_proxies.setdefault(scheme, proxy)
+        scheme = urlparse(prepared_request.url).scheme
+        new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env)
 
         if 'Proxy-Authorization' in headers:
             del headers['Proxy-Authorization']
@@ -633,7 +620,10 @@ class Session(SessionRedirectMixin):
         kwargs.setdefault('stream', self.stream)
         kwargs.setdefault('verify', self.verify)
         kwargs.setdefault('cert', self.cert)
-        kwargs.setdefault('proxies', self.rebuild_proxies(request, self.proxies))
+        if 'proxies' not in kwargs:
+            kwargs['proxies'] = resolve_proxies(
+                request, self.proxies, self.trust_env
+            )
 
         # It's possible that users might accidentally send a Request object.
         # Guard against that specific failure case.
diff --git a/src/pip/_vendor/requests/utils.py b/src/pip/_vendor/requests/utils.py
index fcb996690..1e5857ad8 100644
--- a/src/pip/_vendor/requests/utils.py
+++ b/src/pip/_vendor/requests/utils.py
@@ -21,6 +21,7 @@ import warnings
 import zipfile
 from collections import OrderedDict
 from pip._vendor.urllib3.util import make_headers
+from pip._vendor.urllib3.util import parse_url
 
 from .__version__ import __version__
 from . import certs
@@ -124,7 +125,10 @@ def super_len(o):
     elif hasattr(o, 'fileno'):
         try:
             fileno = o.fileno()
-        except io.UnsupportedOperation:
+        except (io.UnsupportedOperation, AttributeError):
+            # AttributeError is a surprising exception, seeing as how we've just checked
+            # that `hasattr(o, 'fileno')`.  It happens for objects obtained via
+            # `Tarfile.extractfile()`, per issue 5229.
             pass
         else:
             total_length = os.fstat(fileno).st_size
@@ -154,7 +158,7 @@ def super_len(o):
                 current_position = total_length
         else:
             if hasattr(o, 'seek') and total_length is None:
-                # StringIO and BytesIO have seek but no useable fileno
+                # StringIO and BytesIO have seek but no usable fileno
                 try:
                     # seek to end of file
                     o.seek(0, 2)
@@ -251,6 +255,10 @@ def extract_zipped_paths(path):
     archive, member = os.path.split(path)
     while archive and not os.path.exists(archive):
         archive, prefix = os.path.split(archive)
+        if not prefix:
+            # If we don't check for an empty prefix after the split (in other words, archive remains unchanged after the split),
+            # we _can_ end up in an infinite loop on a rare corner case affecting a small number of users
+            break
         member = '/'.join([prefix, member])
 
     if not zipfile.is_zipfile(archive):
@@ -826,6 +834,33 @@ def select_proxy(url, proxies):
     return proxy
 
 
+def resolve_proxies(request, proxies, trust_env=True):
+    """This method takes proxy information from a request and configuration
+    input to resolve a mapping of target proxies. This will consider settings
+    such a NO_PROXY to strip proxy configurations.
+
+    :param request: Request or PreparedRequest
+    :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs
+    :param trust_env: Boolean declaring whether to trust environment configs
+
+    :rtype: dict
+    """
+    proxies = proxies if proxies is not None else {}
+    url = request.url
+    scheme = urlparse(url).scheme
+    no_proxy = proxies.get('no_proxy')
+    new_proxies = proxies.copy()
+
+    if trust_env and not should_bypass_proxies(url, no_proxy=no_proxy):
+        environ_proxies = get_environ_proxies(url, no_proxy=no_proxy)
+
+        proxy = environ_proxies.get(scheme, environ_proxies.get('all'))
+
+        if proxy:
+            new_proxies.setdefault(scheme, proxy)
+    return new_proxies
+
+
 def default_user_agent(name="python-requests"):
     """
     Return a string representing the default user agent.
@@ -928,15 +963,27 @@ def prepend_scheme_if_needed(url, new_scheme):
 
     :rtype: str
     """
-    scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme)
-
-    # urlparse is a finicky beast, and sometimes decides that there isn't a
-    # netloc present. Assume that it's being over-cautious, and switch netloc
-    # and path if urlparse decided there was no netloc.
+    parsed = parse_url(url)
+    scheme, auth, host, port, path, query, fragment = parsed
+
+    # A defect in urlparse determines that there isn't a netloc present in some
+    # urls. We previously assumed parsing was overly cautious, and swapped the
+    # netloc and path. Due to a lack of tests on the original defect, this is
+    # maintained with parse_url for backwards compatibility.
+    netloc = parsed.netloc
     if not netloc:
         netloc, path = path, netloc
 
-    return urlunparse((scheme, netloc, path, params, query, fragment))
+    if auth:
+        # parse_url doesn't provide the netloc with auth
+        # so we'll add it ourselves.
+        netloc = '@'.join([auth, netloc])
+    if scheme is None:
+        scheme = new_scheme
+    if path is None:
+        path = ''
+
+    return urlunparse((scheme, netloc, path, '', query, fragment))
 
 
 def get_auth_from_url(url):
diff --git a/src/pip/_vendor/urllib3/_version.py b/src/pip/_vendor/urllib3/_version.py
index 5141d980b..fa8979d73 100644
--- a/src/pip/_vendor/urllib3/_version.py
+++ b/src/pip/_vendor/urllib3/_version.py
@@ -1,2 +1,2 @@
 # This file is protected via CODEOWNERS
-__version__ = "1.26.7"
+__version__ = "1.26.8"
diff --git a/src/pip/_vendor/urllib3/connection.py b/src/pip/_vendor/urllib3/connection.py
index 60f70f794..4d92ac6d2 100644
--- a/src/pip/_vendor/urllib3/connection.py
+++ b/src/pip/_vendor/urllib3/connection.py
@@ -51,7 +51,6 @@ from .exceptions import (
     SubjectAltNameWarning,
     SystemTimeWarning,
 )
-from .packages.ssl_match_hostname import CertificateError, match_hostname
 from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection
 from .util.ssl_ import (
     assert_fingerprint,
@@ -61,6 +60,7 @@ from .util.ssl_ import (
     resolve_ssl_version,
     ssl_wrap_socket,
 )
+from .util.ssl_match_hostname import CertificateError, match_hostname
 
 log = logging.getLogger(__name__)
 
diff --git a/src/pip/_vendor/urllib3/connectionpool.py b/src/pip/_vendor/urllib3/connectionpool.py
index 8dccf4bc2..15bffcb23 100644
--- a/src/pip/_vendor/urllib3/connectionpool.py
+++ b/src/pip/_vendor/urllib3/connectionpool.py
@@ -2,6 +2,7 @@ from __future__ import absolute_import
 
 import errno
 import logging
+import re
 import socket
 import sys
 import warnings
@@ -35,7 +36,6 @@ from .exceptions import (
 )
 from .packages import six
 from .packages.six.moves import queue
-from .packages.ssl_match_hostname import CertificateError
 from .request import RequestMethods
 from .response import HTTPResponse
 from .util.connection import is_connection_dropped
@@ -44,6 +44,7 @@ from .util.queue import LifoQueue
 from .util.request import set_file_position
 from .util.response import assert_header_parsing
 from .util.retry import Retry
+from .util.ssl_match_hostname import CertificateError
 from .util.timeout import Timeout
 from .util.url import Url, _encode_target
 from .util.url import _normalize_host as normalize_host
@@ -301,8 +302,11 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
             pass
         except queue.Full:
             # This should never happen if self.block == True
-            log.warning("Connection pool is full, discarding connection: %s", self.host)
-
+            log.warning(
+                "Connection pool is full, discarding connection: %s. Connection pool size: %s",
+                self.host,
+                self.pool.qsize(),
+            )
         # Connection never got put back into the pool, close it.
         if conn:
             conn.close()
@@ -745,7 +749,33 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
             # Discard the connection for these exceptions. It will be
             # replaced during the next _get_conn() call.
             clean_exit = False
-            if isinstance(e, (BaseSSLError, CertificateError)):
+
+            def _is_ssl_error_message_from_http_proxy(ssl_error):
+                # We're trying to detect the message 'WRONG_VERSION_NUMBER' but
+                # SSLErrors are kinda all over the place when it comes to the message,
+                # so we try to cover our bases here!
+                message = " ".join(re.split("[^a-z]", str(ssl_error).lower()))
+                return (
+                    "wrong version number" in message or "unknown protocol" in message
+                )
+
+            # Try to detect a common user error with proxies which is to
+            # set an HTTP proxy to be HTTPS when it should be 'http://'
+            # (ie {'http': 'http://proxy', 'https': 'https://proxy'})
+            # Instead we add a nice error message and point to a URL.
+            if (
+                isinstance(e, BaseSSLError)
+                and self.proxy
+                and _is_ssl_error_message_from_http_proxy(e)
+            ):
+                e = ProxyError(
+                    "Your proxy appears to only use HTTP and not HTTPS, "
+                    "try changing your proxy URL to be HTTP. See: "
+                    "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html"
+                    "#https-proxy-error-http-proxy",
+                    SSLError(e),
+                )
+            elif isinstance(e, (BaseSSLError, CertificateError)):
                 e = SSLError(e)
             elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy:
                 e = ProxyError("Cannot connect to proxy.", e)
diff --git a/src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py b/src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py
index 42526be7f..264d564db 100644
--- a/src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py
+++ b/src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py
@@ -48,7 +48,7 @@ from ctypes import (
 )
 from ctypes.util import find_library
 
-from pip._vendor.urllib3.packages.six import raise_from
+from ...packages.six import raise_from
 
 if platform.system() != "Darwin":
     raise ImportError("Only macOS is supported")
diff --git a/src/pip/_vendor/urllib3/packages/__init__.py b/src/pip/_vendor/urllib3/packages/__init__.py
index fce4caa65..e69de29bb 100644
--- a/src/pip/_vendor/urllib3/packages/__init__.py
+++ b/src/pip/_vendor/urllib3/packages/__init__.py
@@ -1,5 +0,0 @@
-from __future__ import absolute_import
-
-from . import ssl_match_hostname
-
-__all__ = ("ssl_match_hostname",)
diff --git a/src/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py b/src/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py
deleted file mode 100644
index ef3fde520..000000000
--- a/src/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import sys
-
-try:
-    # Our match_hostname function is the same as 3.10's, so we only want to
-    # import the match_hostname function if it's at least that good.
-    # We also fallback on Python 3.10+ because our code doesn't emit
-    # deprecation warnings and is the same as Python 3.10 otherwise.
-    if sys.version_info < (3, 5) or sys.version_info >= (3, 10):
-        raise ImportError("Fallback to vendored code")
-
-    from ssl import CertificateError, match_hostname
-except ImportError:
-    try:
-        # Backport of the function from a pypi module
-        from backports.ssl_match_hostname import (  # type: ignore
-            CertificateError,
-            match_hostname,
-        )
-    except ImportError:
-        # Our vendored copy
-        from ._implementation import CertificateError, match_hostname  # type: ignore
-
-# Not needed, but documenting what we provide.
-__all__ = ("CertificateError", "match_hostname")
diff --git a/src/pip/_vendor/urllib3/util/connection.py b/src/pip/_vendor/urllib3/util/connection.py
index facfa0dd2..6af1138f2 100644
--- a/src/pip/_vendor/urllib3/util/connection.py
+++ b/src/pip/_vendor/urllib3/util/connection.py
@@ -2,9 +2,8 @@ from __future__ import absolute_import
 
 import socket
 
-from pip._vendor.urllib3.exceptions import LocationParseError
-
 from ..contrib import _appengine_environ
+from ..exceptions import LocationParseError
 from ..packages import six
 from .wait import NoWayToWaitForSocketError, wait_for_read
 
diff --git a/src/pip/_vendor/urllib3/util/retry.py b/src/pip/_vendor/urllib3/util/retry.py
index c7dc42f1d..3398323fd 100644
--- a/src/pip/_vendor/urllib3/util/retry.py
+++ b/src/pip/_vendor/urllib3/util/retry.py
@@ -69,6 +69,24 @@ class _RetryMeta(type):
         )
         cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT = value
 
+    @property
+    def BACKOFF_MAX(cls):
+        warnings.warn(
+            "Using 'Retry.BACKOFF_MAX' is deprecated and "
+            "will be removed in v2.0. Use 'Retry.DEFAULT_BACKOFF_MAX' instead",
+            DeprecationWarning,
+        )
+        return cls.DEFAULT_BACKOFF_MAX
+
+    @BACKOFF_MAX.setter
+    def BACKOFF_MAX(cls, value):
+        warnings.warn(
+            "Using 'Retry.BACKOFF_MAX' is deprecated and "
+            "will be removed in v2.0. Use 'Retry.DEFAULT_BACKOFF_MAX' instead",
+            DeprecationWarning,
+        )
+        cls.DEFAULT_BACKOFF_MAX = value
+
 
 @six.add_metaclass(_RetryMeta)
 class Retry(object):
@@ -181,7 +199,7 @@ class Retry(object):
 
         seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep
         for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer
-        than :attr:`Retry.BACKOFF_MAX`.
+        than :attr:`Retry.DEFAULT_BACKOFF_MAX`.
 
         By default, backoff is disabled (set to 0).
 
@@ -220,7 +238,7 @@ class Retry(object):
     DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Authorization"])
 
     #: Maximum backoff time.
-    BACKOFF_MAX = 120
+    DEFAULT_BACKOFF_MAX = 120
 
     def __init__(
         self,
@@ -348,7 +366,7 @@ class Retry(object):
             return 0
 
         backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1))
-        return min(self.BACKOFF_MAX, backoff_value)
+        return min(self.DEFAULT_BACKOFF_MAX, backoff_value)
 
     def parse_retry_after(self, retry_after):
         # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4
diff --git a/src/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py b/src/pip/_vendor/urllib3/util/ssl_match_hostname.py
similarity index 96%
rename from src/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py
rename to src/pip/_vendor/urllib3/util/ssl_match_hostname.py
index 689208d3c..a4b4a569c 100644
--- a/src/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py
+++ b/src/pip/_vendor/urllib3/util/ssl_match_hostname.py
@@ -9,7 +9,7 @@ import sys
 # ipaddress has been backported to 2.6+ in pypi.  If it is installed on the
 # system, use it to handle IPAddress ServerAltnames (this was added in
 # python-3.5) otherwise only do DNS matching.  This allows
-# backports.ssl_match_hostname to continue to be used in Python 2.7.
+# util.ssl_match_hostname to continue to be used in Python 2.7.
 try:
     import ipaddress
 except ImportError:
@@ -78,7 +78,8 @@ def _dnsname_match(dn, hostname, max_wildcards=1):
 
 def _to_unicode(obj):
     if isinstance(obj, str) and sys.version_info < (3,):
-        obj = unicode(obj, encoding="ascii", errors="strict")
+        # ignored flake8 # F821 to support python 2.7 function
+        obj = unicode(obj, encoding="ascii", errors="strict")  # noqa: F821
     return obj
 
 
diff --git a/src/pip/_vendor/urllib3/util/ssltransport.py b/src/pip/_vendor/urllib3/util/ssltransport.py
index 0ed97b644..4a7105d17 100644
--- a/src/pip/_vendor/urllib3/util/ssltransport.py
+++ b/src/pip/_vendor/urllib3/util/ssltransport.py
@@ -2,8 +2,8 @@ import io
 import socket
 import ssl
 
-from pip._vendor.urllib3.exceptions import ProxySchemeUnsupported
-from pip._vendor.urllib3.packages import six
+from ..exceptions import ProxySchemeUnsupported
+from ..packages import six
 
 SSL_BLOCKSIZE = 16384
 
diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt
index 338dee56b..03954e245 100644
--- a/src/pip/_vendor/vendor.txt
+++ b/src/pip/_vendor/vendor.txt
@@ -9,11 +9,11 @@ pep517==0.12.0
 platformdirs==2.4.0
 progress==1.6
 pyparsing==2.4.7
-requests==2.26.0
+requests==2.27.1
     certifi==2021.05.30
     chardet==4.0.0
     idna==3.2
-    urllib3==1.26.7
+    urllib3==1.26.8
 rich==10.14.0
     pygments==2.10.0
     typing_extensions==3.10.0.2
diff --git a/tools/vendoring/patches/requests.patch b/tools/vendoring/patches/requests.patch
index 04e6339f4..670c1b0ca 100644
--- a/tools/vendoring/patches/requests.patch
+++ b/tools/vendoring/patches/requests.patch
@@ -81,12 +81,14 @@ index 409b7b028..9e2937167 100644
 
  import sys
 
-@@ -28,10 +25,14 @@ is_py2 = (_ver[0] == 2)
+@@ -28,12 +28,14 @@ is_py2 = (_ver[0] == 2)
  #: Python 3.x?
  is_py3 = (_ver[0] == 3)
 
+-has_simplejson = False
 -try:
 -    import simplejson as json
+-    has_simplejson = True
 -except ImportError:
 -    import json
 +# Note: We've patched out simplejson support in pip because it prevents
@@ -100,6 +102,18 @@ index 409b7b028..9e2937167 100644
 
  # ---------
  # Specifics
+@@ -68,10 +70,7 @@ elif is_py3:
+     # Keep OrderedDict for backwards compatibility.
+     from collections import OrderedDict
+     from collections.abc import Callable, Mapping, MutableMapping
+-    if has_simplejson:
+-        from simplejson import JSONDecodeError
+-    else:
+-        from json import JSONDecodeError
++    from json import JSONDecodeError
+
+     builtin_str = str
+     str = str
 
 diff --git a/src/pip/_vendor/requests/help.py b/src/pip/_vendor/requests/help.py
 index 3a843404c..745f0d7b3 100644


Test patch:
diff --git a/tests/data/indexes/yanked_all/simple/index.html b/tests/data/indexes/yanked_all/simple/index.html
new file mode 100644
index 000000000..732340baa
--- /dev/null
+++ b/tests/data/indexes/yanked_all/simple/index.html
@@ -0,0 +1,7 @@
+<html>
+  <body>
+    <a data-yanked="test reason message" href="../../../packages/simple-1.0.tar.gz">simple-1.0.tar.gz</a>
+    <a data-yanked="test reason message" href="../../../packages/simple-2.0.tar.gz">simple-2.0.tar.gz</a>
+    <a data-yanked="test reason message" href="../../../packages/simple-3.0.tar.gz">simple-3.0.tar.gz</a>
+  </body>
+</html>
diff --git a/tests/functional/test_completion.py b/tests/functional/test_completion.py
index 927ec7ba6..27167ccf8 100644
--- a/tests/functional/test_completion.py
+++ b/tests/functional/test_completion.py
@@ -237,10 +237,10 @@ def test_completion_not_files_after_option(
 ) -> None:
     """
     Test not getting completion files after options which not applicable
-    (e.g. ``pip install``)
+    (e.g. ``pip wheel``)
     """
     res, env = autocomplete(
-        words=("pip install r"),
+        words=("pip wheel r"),
         cword="2",
         cwd=data.completion_paths,
     )
@@ -256,6 +256,24 @@ def test_completion_not_files_after_option(
     ), "autocomplete function completed <dir> when it should not complete"
 
 
+def test_pip_install_complete_files(
+    autocomplete: DoAutocomplete, data: TestData
+) -> None:
+    """``pip install`` autocompletes wheel and sdist files."""
+    res, env = autocomplete(
+        words=("pip install r"),
+        cword="2",
+        cwd=data.completion_paths,
+    )
+    assert all(
+        out in res.stdout
+        for out in (
+            "requirements.txt",
+            "resources",
+        )
+    ), "autocomplete function could not complete <path>"
+
+
 @pytest.mark.parametrize("cl_opts", ["-U", "--user", "-h"])
 def test_completion_not_files_after_nonexpecting_option(
     autocomplete: DoAutocomplete, data: TestData, cl_opts: str
diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py
index a2f1ffb88..6c0ff4d61 100644
--- a/tests/functional/test_install.py
+++ b/tests/functional/test_install.py
@@ -2172,6 +2172,26 @@ def test_install_yanked_file_and_print_warning(
     assert "Successfully installed simple-3.0\n" in result.stdout, str(result)
 
 
+def test_error_all_yanked_files_and_no_pin(
+    script: PipTestEnvironment, data: TestData
+) -> None:
+    """
+    Test raising an error if there are only "yanked" files available and no pin
+    """
+    result = script.pip(
+        "install",
+        "simple",
+        "--index-url",
+        data.index_url("yanked_all"),
+        expect_error=True,
+    )
+    # Make sure an error is raised
+    assert (
+        result.returncode == 1
+        and "ERROR: No matching distribution found for simple\n" in result.stderr
+    ), str(result)
+
+
 @pytest.mark.parametrize(
     "install_args",
     [
diff --git a/tests/functional/test_no_color.py b/tests/functional/test_no_color.py
index 9ead9996a..4094bdd16 100644
--- a/tests/functional/test_no_color.py
+++ b/tests/functional/test_no_color.py
@@ -2,13 +2,16 @@
 Test specific for the --no-color option
 """
 import os
+import shutil
 import subprocess
+import sys
 
 import pytest
 
 from tests.lib import PipTestEnvironment
 
 
+@pytest.mark.skipif(shutil.which("script") is None, reason="no 'script' executable")
 def test_no_color(script: PipTestEnvironment) -> None:
     """Ensure colour output disabled when --no-color is passed."""
     # Using 'script' in this test allows for transparently testing pip's output
@@ -19,12 +22,13 @@ def test_no_color(script: PipTestEnvironment) -> None:
     # 'script' and well as the mere use of the same.
     #
     # This test will stay until someone has the time to rewrite it.
-    command = (
-        "script --flush --quiet --return /tmp/pip-test-no-color.txt "
-        '--command "pip uninstall {} noSuchPackage"'
-    )
+    pip_command = "pip uninstall {} noSuchPackage"
+    if sys.platform == "darwin":
+        command = f"script -q /tmp/pip-test-no-color.txt {pip_command}"
+    else:
+        command = f'script -q /tmp/pip-test-no-color.txt --command "{pip_command}"'
 
-    def get_run_output(option: str) -> str:
+    def get_run_output(option: str = "") -> str:
         cmd = command.format(option)
         proc = subprocess.Popen(
             cmd,
@@ -33,8 +37,6 @@ def test_no_color(script: PipTestEnvironment) -> None:
             stderr=subprocess.PIPE,
         )
         proc.communicate()
-        if proc.returncode:
-            pytest.skip("Unable to capture output using script: " + cmd)
 
         try:
             with open("/tmp/pip-test-no-color.txt") as output_file:
@@ -43,7 +45,5 @@ def test_no_color(script: PipTestEnvironment) -> None:
         finally:
             os.unlink("/tmp/pip-test-no-color.txt")
 
-    assert "\x1b" in get_run_output(option=""), "Expected color in output"
-    assert "\x1b" not in get_run_output(
-        option="--no-color"
-    ), "Expected no color in output"
+    assert "\x1b[3" in get_run_output(""), "Expected color in output"
+    assert "\x1b[3" not in get_run_output("--no-color"), "Expected no color in output"
diff --git a/tests/functional/test_vcs_git.py b/tests/functional/test_vcs_git.py
index 928aadd49..4f22386d5 100644
--- a/tests/functional/test_vcs_git.py
+++ b/tests/functional/test_vcs_git.py
@@ -292,7 +292,10 @@ def test_resolve_commit_not_on_branch(
     # check we can fetch our commit
     rev_options = Git.make_rev_options(commit)
     Git().fetch_new(
-        str(clone_path), HiddenText(repo_path.as_uri(), redacted="*"), rev_options
+        str(clone_path),
+        HiddenText(repo_path.as_uri(), redacted="*"),
+        rev_options,
+        verbosity=0,
     )
 
 
@@ -333,12 +336,14 @@ def test_partial_clone(script: PipTestEnvironment, tmp_path: pathlib.Path) -> No
         str(clone_path1),
         HiddenText(repo_path.as_uri(), redacted="*"),
         Git.make_rev_options(),
+        verbosity=0,
     )
     # Check that we can clone to commit
     Git().fetch_new(
         str(clone_path2),
         HiddenText(repo_path.as_uri(), redacted="*"),
         Git.make_rev_options(commit),
+        verbosity=0,
     )
 
     # Write some additional stuff to git pull
@@ -375,12 +380,14 @@ def test_partial_clone_without_server_support(
         str(clone_path1),
         HiddenText(repo_path.as_uri(), redacted="*"),
         Git.make_rev_options(),
+        verbosity=0,
     )
     # Check that we can clone to commit
     Git().fetch_new(
         str(clone_path2),
         HiddenText(repo_path.as_uri(), redacted="*"),
         Git.make_rev_options(commit),
+        verbosity=0,
     )
 
     # Write some additional stuff to git pull
@@ -414,6 +421,7 @@ def test_clone_without_partial_clone_support(
             str(clone_path),
             HiddenText(repo_path.as_uri(), redacted="*"),
             Git.make_rev_options(),
+            verbosity=0,
         )
 
     repo_file.write_text(u"...")
diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py
index a62969250..9c1b4f442 100644
--- a/tests/lib/__init__.py
+++ b/tests/lib/__init__.py
@@ -250,6 +250,8 @@ class TestFailure(AssertionError):
 
 
 class TestPipResult:
+    __test__ = False
+
     def __init__(self, impl: ProcResult, verbose: bool = False) -> None:
         self._impl = impl
 
diff --git a/tests/lib/local_repos.py b/tests/lib/local_repos.py
index 8d11a3fb1..2ead17aca 100644
--- a/tests/lib/local_repos.py
+++ b/tests/lib/local_repos.py
@@ -55,7 +55,7 @@ def local_checkout(
     else:
         vcs_backend = vcs.get_backend(vcs_name)
         assert vcs_backend is not None
-        vcs_backend.obtain(repo_url_path, url=hide_url(remote_repo))
+        vcs_backend.obtain(repo_url_path, url=hide_url(remote_repo), verbosity=0)
 
     return "{}+{}".format(vcs_name, path_to_url(repo_url_path))
 
diff --git a/tests/unit/resolution_resolvelib/conftest.py b/tests/unit/resolution_resolvelib/conftest.py
index 109b337ff..8f3bba501 100644
--- a/tests/unit/resolution_resolvelib/conftest.py
+++ b/tests/unit/resolution_resolvelib/conftest.py
@@ -46,6 +46,7 @@ def preparer(finder: PackageFinder) -> Iterator[RequirementPreparer]:
                     session=session,
                     finder=finder,
                     use_user_site=False,
+                    verbosity=0,
                 )
 
                 yield preparer
diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py
index 45e6bee10..8f8224dc8 100644
--- a/tests/unit/test_exceptions.py
+++ b/tests/unit/test_exceptions.py
@@ -1,8 +1,10 @@
 """Tests the presentation style of exceptions."""
 
+import io
 import textwrap
 
 import pytest
+from pip._vendor import rich
 
 from pip._internal.exceptions import DiagnosticPipError
 
@@ -57,26 +59,66 @@ class TestDiagnosticPipErrorCreation:
         assert str(exc_info.value) == "error reference must be kebab-case!"
 
 
+def rendered_in_ascii(error: DiagnosticPipError, *, color: bool = False) -> str:
+    with io.BytesIO() as stream:
+        console = rich.console.Console(
+            force_terminal=False,
+            file=io.TextIOWrapper(stream, encoding="ascii", newline=""),
+            color_system="truecolor" if color else None,
+        )
+        console.print(error)
+        return stream.getvalue().decode("ascii")
+
+
 class TestDiagnosticPipErrorPresentation_ASCII:
     def test_complete(self) -> None:
         err = DiagnosticPipError(
             reference="test-diagnostic",
             message="Oh no!\nIt broke. :(",
             context="Something went wrong\nvery wrong.",
-            attention_stmt="You did something wrong, which is what caused this error.",
+            note_stmt="You did something wrong, which is what caused this error.",
             hint_stmt="Do it better next time, by trying harder.",
         )
 
-        assert str(err) == textwrap.dedent(
+        assert rendered_in_ascii(err) == textwrap.dedent(
             """\
+            error: test-diagnostic
+
             Oh no!
             It broke. :(
 
             Something went wrong
             very wrong.
 
-            Note: You did something wrong, which is what caused this error.
-            Hint: Do it better next time, by trying harder.
+            note: You did something wrong, which is what caused this error.
+            hint: Do it better next time, by trying harder.
+            """
+        )
+
+    def test_complete_color(self) -> None:
+        err = DiagnosticPipError(
+            reference="test-diagnostic",
+            message="Oh no!\nIt broke.",
+            context="Something went wrong\nvery wrong.",
+            note_stmt="You did something wrong.",
+            hint_stmt="Do it better next time, by trying harder.",
+        )
+
+        def esc(code: str = "0") -> str:
+            return f"\x1b[{code}m"
+
+        assert rendered_in_ascii(err, color=True) == textwrap.dedent(
+            f"""\
+            {esc("1;31")}error{esc("0")}: {esc("1")}test-diagnostic{esc("0")}
+
+            Oh no!
+            It broke.
+
+            Something went wrong
+            very wrong.
+
+            {esc("1;35")}note{esc("0")}: You did something wrong.
+            {esc("1;36")}hint{esc("0")}: Do it better next time, by trying harder.
             """
         )
 
@@ -85,17 +127,19 @@ class TestDiagnosticPipErrorPresentation_ASCII:
             reference="test-diagnostic",
             message="Oh no!\nIt broke. :(",
             context=None,
-            attention_stmt="You did something wrong, which is what caused this error.",
+            note_stmt="You did something wrong, which is what caused this error.",
             hint_stmt="Do it better next time, by trying harder.",
         )
 
-        assert str(err) == textwrap.dedent(
+        assert rendered_in_ascii(err) == textwrap.dedent(
             """\
+            error: test-diagnostic
+
             Oh no!
             It broke. :(
 
-            Note: You did something wrong, which is what caused this error.
-            Hint: Do it better next time, by trying harder.
+            note: You did something wrong, which is what caused this error.
+            hint: Do it better next time, by trying harder.
             """
         )
 
@@ -104,19 +148,21 @@ class TestDiagnosticPipErrorPresentation_ASCII:
             reference="test-diagnostic",
             message="Oh no!\nIt broke. :(",
             context="Something went wrong\nvery wrong.",
-            attention_stmt=None,
+            note_stmt=None,
             hint_stmt="Do it better next time, by trying harder.",
         )
 
-        assert str(err) == textwrap.dedent(
+        assert rendered_in_ascii(err) == textwrap.dedent(
             """\
+            error: test-diagnostic
+
             Oh no!
             It broke. :(
 
             Something went wrong
             very wrong.
 
-            Hint: Do it better next time, by trying harder.
+            hint: Do it better next time, by trying harder.
             """
         )
 
@@ -125,19 +171,21 @@ class TestDiagnosticPipErrorPresentation_ASCII:
             reference="test-diagnostic",
             message="Oh no!\nIt broke. :(",
             context="Something went wrong\nvery wrong.",
-            attention_stmt="You did something wrong, which is what caused this error.",
+            note_stmt="You did something wrong, which is what caused this error.",
             hint_stmt=None,
         )
 
-        assert str(err) == textwrap.dedent(
+        assert rendered_in_ascii(err) == textwrap.dedent(
             """\
+            error: test-diagnostic
+
             Oh no!
             It broke. :(
 
             Something went wrong
             very wrong.
 
-            Note: You did something wrong, which is what caused this error.
+            note: You did something wrong, which is what caused this error.
             """
         )
 
@@ -146,16 +194,18 @@ class TestDiagnosticPipErrorPresentation_ASCII:
             reference="test-diagnostic",
             message="Oh no!\nIt broke. :(",
             context=None,
-            attention_stmt="You did something wrong, which is what caused this error.",
+            note_stmt="You did something wrong, which is what caused this error.",
             hint_stmt=None,
         )
 
-        assert str(err) == textwrap.dedent(
+        assert rendered_in_ascii(err) == textwrap.dedent(
             """\
+            error: test-diagnostic
+
             Oh no!
             It broke. :(
 
-            Note: You did something wrong, which is what caused this error.
+            note: You did something wrong, which is what caused this error.
             """
         )
 
@@ -164,16 +214,18 @@ class TestDiagnosticPipErrorPresentation_ASCII:
             reference="test-diagnostic",
             message="Oh no!\nIt broke. :(",
             context=None,
-            attention_stmt=None,
+            note_stmt=None,
             hint_stmt="Do it better next time, by trying harder.",
         )
 
-        assert str(err) == textwrap.dedent(
+        assert rendered_in_ascii(err) == textwrap.dedent(
             """\
+            error: test-diagnostic
+
             Oh no!
             It broke. :(
 
-            Hint: Do it better next time, by trying harder.
+            hint: Do it better next time, by trying harder.
             """
         )
 
@@ -182,12 +234,14 @@ class TestDiagnosticPipErrorPresentation_ASCII:
             reference="test-diagnostic",
             message="Oh no!\nIt broke. :(",
             context="Something went wrong\nvery wrong.",
-            attention_stmt=None,
+            note_stmt=None,
             hint_stmt=None,
         )
 
-        assert str(err) == textwrap.dedent(
+        assert rendered_in_ascii(err) == textwrap.dedent(
             """\
+            error: test-diagnostic
+
             Oh no!
             It broke. :(
 
@@ -202,12 +256,219 @@ class TestDiagnosticPipErrorPresentation_ASCII:
             message="Oh no!\nIt broke. :(",
             context=None,
             hint_stmt=None,
-            attention_stmt=None,
+            note_stmt=None,
         )
 
-        assert str(err) == textwrap.dedent(
+        assert rendered_in_ascii(err) == textwrap.dedent(
             """\
+            error: test-diagnostic
+
             Oh no!
             It broke. :(
             """
         )
+
+
+def rendered(error: DiagnosticPipError, *, color: bool = False) -> str:
+    with io.StringIO() as stream:
+        console = rich.console.Console(
+            force_terminal=False,
+            file=stream,
+            color_system="truecolor" if color else None,
+        )
+        console.print(error)
+        return stream.getvalue()
+
+
+class TestDiagnosticPipErrorPresentation_Unicode:
+    def test_complete(self) -> None:
+        err = DiagnosticPipError(
+            reference="test-diagnostic",
+            message="Oh no!\nIt broke. :(",
+            context="Something went wrong\nvery wrong.",
+            note_stmt="You did something wrong, which is what caused this error.",
+            hint_stmt="Do it better next time, by trying harder.",
+        )
+
+        assert rendered(err) == textwrap.dedent(
+            """\
+            error: test-diagnostic
+
+            × Oh no!
+            │ It broke. :(
+            ╰─> Something went wrong
+                very wrong.
+
+            note: You did something wrong, which is what caused this error.
+            hint: Do it better next time, by trying harder.
+            """
+        )
+
+    def test_complete_color(self) -> None:
+        err = DiagnosticPipError(
+            reference="test-diagnostic",
+            message="Oh no!\nIt broke.",
+            context="Something went wrong\nvery wrong.",
+            note_stmt="You did something wrong.",
+            hint_stmt="Do it better next time, by trying harder.",
+        )
+
+        def esc(code: str = "0") -> str:
+            return f"\x1b[{code}m"
+
+        assert rendered(err, color=True) == textwrap.dedent(
+            f"""\
+            {esc("1;31")}error{esc("0")}: {esc("1")}test-diagnostic{esc("0")}
+
+            {esc("31")}×{esc("0")} Oh no!
+            {esc("31")}│{esc("0")} It broke.
+            {esc("31")}╰─>{esc("0")} Something went wrong
+            {esc("31")}   {esc("0")} very wrong.
+
+            {esc("1;35")}note{esc("0")}: You did something wrong.
+            {esc("1;36")}hint{esc("0")}: Do it better next time, by trying harder.
+            """
+        )
+
+    def test_no_context(self) -> None:
+        err = DiagnosticPipError(
+            reference="test-diagnostic",
+            message="Oh no!\nIt broke. :(",
+            context=None,
+            note_stmt="You did something wrong, which is what caused this error.",
+            hint_stmt="Do it better next time, by trying harder.",
+        )
+
+        assert rendered(err) == textwrap.dedent(
+            """\
+            error: test-diagnostic
+
+            × Oh no!
+              It broke. :(
+
+            note: You did something wrong, which is what caused this error.
+            hint: Do it better next time, by trying harder.
+            """
+        )
+
+    def test_no_note(self) -> None:
+        err = DiagnosticPipError(
+            reference="test-diagnostic",
+            message="Oh no!\nIt broke. :(",
+            context="Something went wrong\nvery wrong.",
+            note_stmt=None,
+            hint_stmt="Do it better next time, by trying harder.",
+        )
+
+        assert rendered(err) == textwrap.dedent(
+            """\
+            error: test-diagnostic
+
+            × Oh no!
+            │ It broke. :(
+            ╰─> Something went wrong
+                very wrong.
+
+            hint: Do it better next time, by trying harder.
+            """
+        )
+
+    def test_no_hint(self) -> None:
+        err = DiagnosticPipError(
+            reference="test-diagnostic",
+            message="Oh no!\nIt broke. :(",
+            context="Something went wrong\nvery wrong.",
+            note_stmt="You did something wrong, which is what caused this error.",
+            hint_stmt=None,
+        )
+
+        assert rendered(err) == textwrap.dedent(
+            """\
+            error: test-diagnostic
+
+            × Oh no!
+            │ It broke. :(
+            ╰─> Something went wrong
+                very wrong.
+
+            note: You did something wrong, which is what caused this error.
+            """
+        )
+
+    def test_no_context_no_hint(self) -> None:
+        err = DiagnosticPipError(
+            reference="test-diagnostic",
+            message="Oh no!\nIt broke. :(",
+            context=None,
+            note_stmt="You did something wrong, which is what caused this error.",
+            hint_stmt=None,
+        )
+
+        assert rendered(err) == textwrap.dedent(
+            """\
+            error: test-diagnostic
+
+            × Oh no!
+              It broke. :(
+
+            note: You did something wrong, which is what caused this error.
+            """
+        )
+
+    def test_no_context_no_note(self) -> None:
+        err = DiagnosticPipError(
+            reference="test-diagnostic",
+            message="Oh no!\nIt broke. :(",
+            context=None,
+            note_stmt=None,
+            hint_stmt="Do it better next time, by trying harder.",
+        )
+
+        assert rendered(err) == textwrap.dedent(
+            """\
+            error: test-diagnostic
+
+            × Oh no!
+              It broke. :(
+
+            hint: Do it better next time, by trying harder.
+            """
+        )
+
+    def test_no_hint_no_note(self) -> None:
+        err = DiagnosticPipError(
+            reference="test-diagnostic",
+            message="Oh no!\nIt broke. :(",
+            context="Something went wrong\nvery wrong.",
+            note_stmt=None,
+            hint_stmt=None,
+        )
+
+        assert rendered(err) == textwrap.dedent(
+            """\
+            error: test-diagnostic
+
+            × Oh no!
+            │ It broke. :(
+            ╰─> Something went wrong
+                very wrong.
+            """
+        )
+
+    def test_no_hint_no_note_no_context(self) -> None:
+        err = DiagnosticPipError(
+            reference="test-diagnostic",
+            message="Oh no!\nIt broke. :(",
+            context=None,
+            hint_stmt=None,
+            note_stmt=None,
+        )
+
+        assert rendered(err) == textwrap.dedent(
+            """\
+            error: test-diagnostic
+
+            × Oh no!
+              It broke. :(
+            """
+        )
diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py
index 7b90d5dcc..4f0447931 100644
--- a/tests/unit/test_logging.py
+++ b/tests/unit/test_logging.py
@@ -6,8 +6,8 @@ import pytest
 
 from pip._internal.utils.logging import (
     BrokenStdoutLoggingError,
-    ColorizedStreamHandler,
     IndentingFormatter,
+    RichPipStreamHandler,
     indent_log,
 )
 from pip._internal.utils.misc import captured_stderr, captured_stdout
@@ -142,7 +142,7 @@ class TestColorizedStreamHandler:
         record = self._make_log_record()
 
         with captured_stderr() as stderr:
-            handler = ColorizedStreamHandler(stream=stderr)
+            handler = RichPipStreamHandler(stream=stderr, no_color=True)
             with patch("sys.stderr.flush") as mock_flush:
                 mock_flush.side_effect = BrokenPipeError()
                 # The emit() call raises no exception.
@@ -165,7 +165,7 @@ class TestColorizedStreamHandler:
         record = self._make_log_record()
 
         with captured_stdout() as stdout:
-            handler = ColorizedStreamHandler(stream=stdout)
+            handler = RichPipStreamHandler(stream=stdout, no_color=True)
             with patch("sys.stdout.write") as mock_write:
                 mock_write.side_effect = BrokenPipeError()
                 with pytest.raises(BrokenStdoutLoggingError):
@@ -180,7 +180,7 @@ class TestColorizedStreamHandler:
         record = self._make_log_record()
 
         with captured_stdout() as stdout:
-            handler = ColorizedStreamHandler(stream=stdout)
+            handler = RichPipStreamHandler(stream=stdout, no_color=True)
             with patch("sys.stdout.flush") as mock_flush:
                 mock_flush.side_effect = BrokenPipeError()
                 with pytest.raises(BrokenStdoutLoggingError):
diff --git a/tests/unit/test_operations_prepare.py b/tests/unit/test_operations_prepare.py
index 961b96ca7..a2ee87870 100644
--- a/tests/unit/test_operations_prepare.py
+++ b/tests/unit/test_operations_prepare.py
@@ -44,6 +44,7 @@ def test_unpack_url_with_urllib_response_without_content_type(data: TestData) ->
             temp_dir,
             download=download,
             download_dir=None,
+            verbosity=0,
         )
         assert set(os.listdir(temp_dir)) == {
             "PKG-INFO",
@@ -188,7 +189,7 @@ class Test_unpack_url:
 
     def test_unpack_url_no_download(self, tmpdir: Path, data: TestData) -> None:
         self.prep(tmpdir, data)
-        unpack_url(self.dist_url, self.build_dir, self.no_download)
+        unpack_url(self.dist_url, self.build_dir, self.no_download, verbosity=0)
         assert os.path.isdir(os.path.join(self.build_dir, "simple"))
         assert not os.path.isfile(os.path.join(self.download_dir, self.dist_file))
 
@@ -205,6 +206,7 @@ class Test_unpack_url:
                 self.build_dir,
                 download=self.no_download,
                 hashes=Hashes({"md5": ["bogus"]}),
+                verbosity=0,
             )
 
     def test_unpack_url_thats_a_dir(self, tmpdir: Path, data: TestData) -> None:
@@ -216,6 +218,7 @@ class Test_unpack_url:
             self.build_dir,
             download=self.no_download,
             download_dir=self.download_dir,
+            verbosity=0,
         )
         assert os.path.isdir(os.path.join(self.build_dir, "fspkg"))
 
@@ -241,7 +244,13 @@ def test_unpack_url_excludes_expected_dirs(tmpdir: Path, exclude_dir: str) -> No
     dst_included_dir = dst_dir.joinpath("subdir", exclude_dir)
 
     src_link = Link(path_to_url(src_dir))
-    unpack_url(src_link, dst_dir, Mock(side_effect=AssertionError), download_dir=None)
+    unpack_url(
+        src_link,
+        dst_dir,
+        Mock(side_effect=AssertionError),
+        download_dir=None,
+        verbosity=0,
+    )
     assert not os.path.isdir(dst_excluded_dir)
     assert not os.path.isfile(dst_excluded_file)
     assert os.path.isfile(dst_included_file)
diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py
index 53d9c9ef3..858695c1f 100644
--- a/tests/unit/test_req.py
+++ b/tests/unit/test_req.py
@@ -98,6 +98,7 @@ class TestRequirementSet:
                 require_hashes=require_hashes,
                 use_user_site=False,
                 lazy_wheel=False,
+                verbosity=0,
                 in_tree_build=False,
             )
             yield Resolver(
diff --git a/tests/unit/test_vcs.py b/tests/unit/test_vcs.py
index 67e3273c7..64f60bc40 100644
--- a/tests/unit/test_vcs.py
+++ b/tests/unit/test_vcs.py
@@ -764,12 +764,12 @@ class TestSubversionArgs(TestCase):
         assert self.call_subprocess_mock.call_args[0][0] == args
 
     def test_obtain(self) -> None:
-        self.svn.obtain(self.dest, hide_url(self.url))
+        self.svn.obtain(self.dest, hide_url(self.url), verbosity=0)
         self.assert_call_args(
             [
                 "svn",
                 "checkout",
-                "-q",
+                "--quiet",
                 "--non-interactive",
                 "--username",
                 "username",
@@ -781,12 +781,12 @@ class TestSubversionArgs(TestCase):
         )
 
     def test_fetch_new(self) -> None:
-        self.svn.fetch_new(self.dest, hide_url(self.url), self.rev_options)
+        self.svn.fetch_new(self.dest, hide_url(self.url), self.rev_options, verbosity=0)
         self.assert_call_args(
             [
                 "svn",
                 "checkout",
-                "-q",
+                "--quiet",
                 "--non-interactive",
                 hide_url("svn+http://username:password@svn.example.com/"),
                 "/tmp/test",
@@ -795,12 +795,12 @@ class TestSubversionArgs(TestCase):
 
     def test_fetch_new_revision(self) -> None:
         rev_options = RevOptions(Subversion, "123")
-        self.svn.fetch_new(self.dest, hide_url(self.url), rev_options)
+        self.svn.fetch_new(self.dest, hide_url(self.url), rev_options, verbosity=0)
         self.assert_call_args(
             [
                 "svn",
                 "checkout",
-                "-q",
+                "--quiet",
                 "--non-interactive",
                 "-r",
                 "123",