diff --git a/backport-CVE-2023-45803-Made-body-stripped-from-HTTP-requests.patch b/backport-CVE-2023-45803-Made-body-stripped-from-HTTP-requests.patch deleted file mode 100644 index e7a6cae784cc3e592012189b4697ff0974837bb1..0000000000000000000000000000000000000000 --- a/backport-CVE-2023-45803-Made-body-stripped-from-HTTP-requests.patch +++ /dev/null @@ -1,99 +0,0 @@ -From b594c5ceaca38e1ac215f916538fb128e3526a36 Mon Sep 17 00:00:00 2001 -From: Illia Volochii -Date: Tue, 17 Oct 2023 19:35:39 +0300 -Subject: [PATCH] Merge pull request from GHSA-g4mx-q9vg-27p4 - -Conflict:Files dummyserver/handlers.py, test/with_dummyserver/test_connectionpool.py -and test/with_dummyserver/test_poolmanager.py do not exist. Therefore, no dummy server -and test case is involved. -Reference:https://github.com/urllib3/urllib3/commit/b594c5ceaca38e1ac215f916538fb128e3526a36 - ---- - src/pip/_vendor/urllib3/_collections.py | 18 ++++++++++++++++++ - src/pip/_vendor/urllib3/connectionpool.py | 5 +++++ - src/pip/_vendor/urllib3/poolmanager.py | 7 +++++-- - 3 files changed, 28 insertions(+), 2 deletions(-) - -diff --git a/src/pip/_vendor/urllib3/_collections.py b/src/pip/_vendor/urllib3/_collections.py -index da9857e..bceb845 100644 ---- a/src/pip/_vendor/urllib3/_collections.py -+++ b/src/pip/_vendor/urllib3/_collections.py -@@ -268,6 +268,24 @@ class HTTPHeaderDict(MutableMapping): - else: - return vals[1:] - -+ def _prepare_for_method_change(self): -+ """ -+ Remove content-specific header fields before changing the request -+ method to GET or HEAD according to RFC 9110, Section 15.4. -+ """ -+ content_specific_headers = [ -+ "Content-Encoding", -+ "Content-Language", -+ "Content-Location", -+ "Content-Type", -+ "Content-Length", -+ "Digest", -+ "Last-Modified", -+ ] -+ for header in content_specific_headers: -+ self.discard(header) -+ return self -+ - # Backwards compatibility for httplib - getheaders = getlist - getallmatchingheaders = getlist -diff --git a/src/pip/_vendor/urllib3/connectionpool.py b/src/pip/_vendor/urllib3/connectionpool.py -index 96844d9..5a6adcb 100644 ---- a/src/pip/_vendor/urllib3/connectionpool.py -+++ b/src/pip/_vendor/urllib3/connectionpool.py -@@ -9,6 +9,7 @@ import warnings - from socket import error as SocketError - from socket import timeout as SocketTimeout - -+from ._collections import HTTPHeaderDict - from .connection import ( - BaseSSLError, - BrokenPipeError, -@@ -843,7 +844,11 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): - redirect_location = redirect and response.get_redirect_location() - if redirect_location: - if response.status == 303: -+ # Change the method according to RFC 9110, Section 15.4.4. - method = "GET" -+ # And lose the body not to transfer anything sensitive. -+ body = None -+ headers = HTTPHeaderDict(headers)._prepare_for_method_change() - - try: - retries = retries.increment(method, url, response=response, _pool=self) -diff --git a/src/pip/_vendor/urllib3/poolmanager.py b/src/pip/_vendor/urllib3/poolmanager.py -index 14b10da..fb51bf7 100644 ---- a/src/pip/_vendor/urllib3/poolmanager.py -+++ b/src/pip/_vendor/urllib3/poolmanager.py -@@ -4,7 +4,7 @@ import collections - import functools - import logging - --from ._collections import RecentlyUsedContainer -+from ._collections import HTTPHeaderDict, RecentlyUsedContainer - from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme - from .exceptions import ( - LocationValueError, -@@ -382,9 +382,12 @@ class PoolManager(RequestMethods): - # Support relative URLs for redirecting. - redirect_location = urljoin(url, redirect_location) - -- # RFC 7231, Section 6.4.4 - if response.status == 303: -+ # Change the method according to RFC 9110, Section 15.4.4. - method = "GET" -+ # And lose the body not to transfer anything sensitive. -+ kw["body"] = None -+ kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change() - - retries = kw.get("retries") - if not isinstance(retries, Retry): --- -2.26.2.windows.1 - diff --git a/backport-CVE-2024-37891-Strip-Proxy-Authorization-header-on-redirects.patch b/backport-CVE-2024-37891-Strip-Proxy-Authorization-header-on-redirects.patch deleted file mode 100644 index 4f8a34f2a524ca5993bc21c2f5df43e4f20b550e..0000000000000000000000000000000000000000 --- a/backport-CVE-2024-37891-Strip-Proxy-Authorization-header-on-redirects.patch +++ /dev/null @@ -1,33 +0,0 @@ -From accff72ecc2f6cf5a76d9570198a93ac7c90270e Mon Sep 17 00:00:00 2001 -From: Quentin Pradet -Date: Mon, 17 Jun 2024 11:09:06 +0400 -Subject: [PATCH] Merge pull request from GHSA-34jh-p97f-mpxf - -* Strip Proxy-Authorization header on redirects - -Conflict:Files test/test_retry.py and test/with_dummyserver/test_poolmanager.py do not -exist. Therefore, no test case is involved. -Reference:https://github.com/urllib3/urllib3/commit/accff72ecc2f6cf5a76d9570198a93ac7c90270e - ---- - src/pip/_vendor/urllib3/util/retry.py | 4 +++- - 1 file changed, 3 insertions(+), 1 deletion(-) - -diff --git a/src/pip/_vendor/urllib3/util/retry.py b/src/pip/_vendor/urllib3/util/retry.py -index 60ef6c4..9a1e90d 100644 ---- a/src/pip/_vendor/urllib3/util/retry.py -+++ b/src/pip/_vendor/urllib3/util/retry.py -@@ -235,7 +235,9 @@ class Retry(object): - RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) - - #: Default headers to be used for ``remove_headers_on_redirect`` -- DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Cookie", "Authorization"]) -+ DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset( -+ ["Cookie", "Authorization", "Proxy-Authorization"] -+ ) - - #: Maximum backoff time. - DEFAULT_BACKOFF_MAX = 120 --- -2.26.2.windows.1 - diff --git a/backport-CVE-2024-47081.patch b/backport-CVE-2024-47081.patch deleted file mode 100644 index 986ac410e5b615bfdf2ba07f7dfaffe164b1dbd1..0000000000000000000000000000000000000000 --- a/backport-CVE-2024-47081.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 96ba401c1296ab1dda74a2365ef36d88f7d144ef Mon Sep 17 00:00:00 2001 -From: Nate Prewitt -Date: Wed, 25 Sep 2024 08:03:20 -0700 -Subject: [PATCH] Only use hostname to do netrc lookup instead of netloc - -Conflict:NA -Reference:https://github.com/psf/requests/commit/96ba401c1296ab1dda74a2365ef36d88f7d144ef - ---- - src/pip/_vendor/requests/utils.py | 8 +------- - 1 file changed, 1 insertion(+), 7 deletions(-) - -diff --git a/src/pip/_vendor/requests/utils.py b/src/pip/_vendor/requests/utils.py -index fcb9966..abffd5b 100644 ---- a/src/pip/_vendor/requests/utils.py -+++ b/src/pip/_vendor/requests/utils.py -@@ -204,13 +204,7 @@ def get_netrc_auth(url, raise_errors=False): - return - - ri = urlparse(url) -- -- # Strip port numbers from netloc. This weird `if...encode`` dance is -- # used for Python 3.2, which doesn't support unicode literals. -- splitstr = b":" -- if isinstance(url, str): -- splitstr = splitstr.decode("ascii") -- host = ri.netloc.split(splitstr)[0] -+ host = ri.hostname - - try: - _netrc = netrc(netrc_path).authenticators(host) --- -2.33.0 - diff --git a/backport-CVE-2025-50181.patch b/backport-CVE-2025-50181.patch deleted file mode 100644 index 1235da75d55cc2f2088779b18a036e05703f04e0..0000000000000000000000000000000000000000 --- a/backport-CVE-2025-50181.patch +++ /dev/null @@ -1,90 +0,0 @@ -From f05b1329126d5be6de501f9d1e3e36738bc08857 Mon Sep 17 00:00:00 2001 -From: Illia Volochii -Date: Wed, 18 Jun 2025 16:25:01 +0300 -Subject: [PATCH] Merge commit from fork - -* Apply Quentin's suggestion - -Co-authored-by: Quentin Pradet - -* Add tests for disabled redirects in the pool manager - -* Add a possible fix for the issue with not raised `MaxRetryError` - -* Make urllib3 handle redirects instead of JS when JSPI is used - -* Fix info in the new comment - -* State that redirects with XHR are not controlled by urllib3 - -* Remove excessive params from new test requests - -* Add tests reaching max non-0 redirects - -* Test redirects with Emscripten - -* Fix `test_merge_pool_kwargs` - -* Add a changelog entry - -* Parametrize tests - -* Drop a fix for Emscripten - -* Apply Seth's suggestion to docs - -Co-authored-by: Seth Michael Larson - -* Use a minor release instead of the patch one - -Reference:https://github.com/urllib3/urllib3/commit/f05b1329126d5be6de501f9d1e3e36738bc08857 -Conflict:test/with_dummyserver/test_poolmanger, -test/contrib/emscripten/test_emscrepten.py has not been modified because -is has been deleted in the pre-phase of the spec file;CHANGES.rst, -docs/reference/contrib/emscripten.rst,dummyserver/app.py has not been -modified because these are the latest features and informations, does -not involve related modifications - ---- - src/pip/_vendor/urllib3/poolmanager.py | 18 +++++++++++++++++- - 1 file changed, 17 insertions(+), 1 deletion(-) - -diff --git a/src/pip/_vendor/urllib3/poolmanager.py b/src/pip/_vendor/urllib3/poolmanager.py -index fb51bf7..a8de7c6 100644 ---- a/src/pip/_vendor/urllib3/poolmanager.py -+++ b/src/pip/_vendor/urllib3/poolmanager.py -@@ -170,6 +170,22 @@ class PoolManager(RequestMethods): - - def __init__(self, num_pools=10, headers=None, **connection_pool_kw): - RequestMethods.__init__(self, headers) -+ if "retries" in connection_pool_kw: -+ retries = connection_pool_kw["retries"] -+ if not isinstance(retries, Retry): -+ # When Retry is initialized, raise_on_redirect is based -+ # on a redirect boolean value. -+ # But requests made via a pool manager always set -+ # redirect to False, and raise_on_redirect always ends -+ # up being False consequently. -+ # Here we fix the issue by setting raise_on_redirect to -+ # a value needed by the pool manager without considering -+ # the redirect boolean. -+ raise_on_redirect = retries is not False -+ retries = Retry.from_int(retries, redirect=False) -+ retries.raise_on_redirect = raise_on_redirect -+ connection_pool_kw = connection_pool_kw.copy() -+ connection_pool_kw["retries"] = retries - self.connection_pool_kw = connection_pool_kw - self.pools = RecentlyUsedContainer(num_pools) - -@@ -389,7 +405,7 @@ class PoolManager(RequestMethods): - kw["body"] = None - kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change() - -- retries = kw.get("retries") -+ retries = kw.get("retries", response.retries) - if not isinstance(retries, Retry): - retries = Retry.from_int(retries, redirect=redirect) - --- -2.33.0 - diff --git a/dummy-certifi.patch b/dummy-certifi.patch deleted file mode 100644 index 8896ce80f2f64e8941e99d875eab4f1208f327ff..0000000000000000000000000000000000000000 --- a/dummy-certifi.patch +++ /dev/null @@ -1,128 +0,0 @@ -From 09c983fdeabe3fa0b90b73f32ddf84a61e498e09 Mon Sep 17 00:00:00 2001 -From: Karolina Surma -Date: Tue, 15 Nov 2022 09:22:46 +0100 -Subject: [PATCH] Dummy certifi patch - ---- - src/pip/_vendor/certifi/core.py | 105 ++------------------------------ - 1 file changed, 6 insertions(+), 99 deletions(-) - -diff --git a/src/pip/_vendor/certifi/core.py b/src/pip/_vendor/certifi/core.py -index c3e5466..eb297f7 100644 ---- a/src/pip/_vendor/certifi/core.py -+++ b/src/pip/_vendor/certifi/core.py -@@ -4,105 +4,12 @@ certifi.py - - This module returns the installation location of cacert.pem or its contents. - """ --import sys - -+# The RPM-packaged certifi always uses the system certificates -+def where() -> str: -+ return '/etc/pki/tls/certs/ca-bundle.crt' - --if sys.version_info >= (3, 11): -+def contents() -> str: -+ with open(where(), encoding='utf=8') as data: -+ return data.read() - -- from importlib.resources import as_file, files -- -- _CACERT_CTX = None -- _CACERT_PATH = None -- -- def where() -> str: -- # This is slightly terrible, but we want to delay extracting the file -- # in cases where we're inside of a zipimport situation until someone -- # actually calls where(), but we don't want to re-extract the file -- # on every call of where(), so we'll do it once then store it in a -- # global variable. -- global _CACERT_CTX -- global _CACERT_PATH -- if _CACERT_PATH is None: -- # This is slightly janky, the importlib.resources API wants you to -- # manage the cleanup of this file, so it doesn't actually return a -- # path, it returns a context manager that will give you the path -- # when you enter it and will do any cleanup when you leave it. In -- # the common case of not needing a temporary file, it will just -- # return the file system location and the __exit__() is a no-op. -- # -- # We also have to hold onto the actual context manager, because -- # it will do the cleanup whenever it gets garbage collected, so -- # we will also store that at the global level as well. -- _CACERT_CTX = as_file(files("pip._vendor.certifi").joinpath("cacert.pem")) -- _CACERT_PATH = str(_CACERT_CTX.__enter__()) -- -- return _CACERT_PATH -- -- def contents() -> str: -- return files("pip._vendor.certifi").joinpath("cacert.pem").read_text(encoding="ascii") -- --elif sys.version_info >= (3, 7): -- -- from importlib.resources import path as get_path, read_text -- -- _CACERT_CTX = None -- _CACERT_PATH = None -- -- def where() -> str: -- # This is slightly terrible, but we want to delay extracting the -- # file in cases where we're inside of a zipimport situation until -- # someone actually calls where(), but we don't want to re-extract -- # the file on every call of where(), so we'll do it once then store -- # it in a global variable. -- global _CACERT_CTX -- global _CACERT_PATH -- if _CACERT_PATH is None: -- # This is slightly janky, the importlib.resources API wants you -- # to manage the cleanup of this file, so it doesn't actually -- # return a path, it returns a context manager that will give -- # you the path when you enter it and will do any cleanup when -- # you leave it. In the common case of not needing a temporary -- # file, it will just return the file system location and the -- # __exit__() is a no-op. -- # -- # We also have to hold onto the actual context manager, because -- # it will do the cleanup whenever it gets garbage collected, so -- # we will also store that at the global level as well. -- _CACERT_CTX = get_path("pip._vendor.certifi", "cacert.pem") -- _CACERT_PATH = str(_CACERT_CTX.__enter__()) -- -- return _CACERT_PATH -- -- def contents() -> str: -- return read_text("pip._vendor.certifi", "cacert.pem", encoding="ascii") -- --else: -- import os -- import types -- from typing import Union -- -- Package = Union[types.ModuleType, str] -- Resource = Union[str, "os.PathLike"] -- -- # This fallback will work for Python versions prior to 3.7 that lack the -- # importlib.resources module but relies on the existing `where` function -- # so won't address issues with environments like PyOxidizer that don't set -- # __file__ on modules. -- def read_text( -- package: Package, -- resource: Resource, -- encoding: str = 'utf-8', -- errors: str = 'strict' -- ) -> str: -- with open(where(), encoding=encoding) as data: -- return data.read() -- -- # If we don't have importlib.resources, then we will just do the old logic -- # of assuming we're on the filesystem and munge the path directly. -- def where() -> str: -- f = os.path.dirname(__file__) -- -- return os.path.join(f, "cacert.pem") -- -- def contents() -> str: -- return read_text("pip._vendor.certifi", "cacert.pem", encoding="ascii") --- -2.37.3 - diff --git a/pip-23.3.1.tar.gz b/pip-23.3.1.tar.gz deleted file mode 100644 index 6eb1137a5b69926108d80f741a0c2186a2d514b7..0000000000000000000000000000000000000000 Binary files a/pip-23.3.1.tar.gz and /dev/null differ diff --git a/pip-25.3.tar.gz b/pip-25.3.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..dd946656907df6a71b6b23982f0d772716912102 Binary files /dev/null and b/pip-25.3.tar.gz differ diff --git a/python-pip.spec b/python-pip.spec index 6549dd46e5eabee19803c2ca7c44e1937c82649e..fd5e28efaa01e4b06fbf4dfaa092e98372e23d29 100644 --- a/python-pip.spec +++ b/python-pip.spec @@ -5,30 +5,25 @@ pip is the package installer for Python. You can use pip to install packages from the Python Package Index and other indexes. %global bashcompdir %(b=$(pkg-config --variable=completionsdir bash-completion 2>/dev/null); echo ${b:-%{_sysconfdir}/bash_completion.d}) Name: python-%{srcname} -Version: 23.3.1 -Release: 5 +Version: 25.3 +Release: 1 Summary: A tool for installing and managing Python packages License: MIT and Python and ASL 2.0 and BSD and ISC and LGPLv2 and MPLv2.0 and (ASL 2.0 or BSD) URL: http://www.pip-installer.org Source0: %{pypi_source} Source1: pip.loongarch.conf +Source10: pip-allow-older-versions.patch BuildArch: noarch -Patch1: remove-existing-dist-only-if-path-conflicts.patch -Patch6000: dummy-certifi.patch -Patch6001: backport-CVE-2023-45803-Made-body-stripped-from-HTTP-requests.patch -Patch6002: backport-CVE-2024-37891-Strip-Proxy-Authorization-header-on-redirects.patch -Patch6003: backport-CVE-2024-47081.patch -Patch6004: backport-CVE-2025-50181.patch - -Source10: pip-allow-older-versions.patch %description %{_description} %package -n python%{python3_pkgversion}-%{srcname} Summary: %{summary} -BuildRequires: python%{python3_pkgversion}-devel python%{python3_pkgversion}-setuptools bash-completion ca-certificates +BuildRequires: python%{python3_pkgversion}-devel python%{python3_pkgversion}-setuptools bash-completion ca-certificates +BuildRequires: python3-pip Requires: python%{python3_pkgversion}-setuptools ca-certificates BuildRequires: python%{python3_pkgversion}-wheel +BuildRequires: python3-flit-core %{?python_provide:%python_provide python%{python3_pkgversion}-%{srcname}} Provides: python%{python3_pkgversion}dist(pip) = %{version} Provides: python%{python3_version}dist(pip) = %{version} @@ -53,21 +48,13 @@ sed -i '/html_theme = "furo"/d' docs/html/conf.py # Remove windows executable binaries rm -v src/pip/_vendor/distlib/*.exe -sed -i '/\.exe/d' setup.py %build -%py3_build_wheel +%pyproject_build %install -%{__python3} dist/%{python_wheelname}/pip install \ - --root %{buildroot} \ - --no-deps \ - --no-cache-dir \ - --no-index \ - --ignore-installed \ - --find-links dist \ - 'pip==%{version}' +%pyproject_install %if %{with doc} pushd docs/build/man @@ -106,7 +93,6 @@ echo rpm > %{buildroot}%{python3_sitelib}/pip-%{version}.dist-info/INSTALLER rm %{buildroot}%{python3_sitelib}/pip-%{version}.dist-info/RECORD mkdir -p %{buildroot}%{python_wheeldir} -install -p dist/%{python_wheelname} -t %{buildroot}%{python_wheeldir} # Set default pip mirror via pip.conf %ifarch loongarch64 @@ -133,9 +119,16 @@ install -D -m0644 %{SOURCE1} %{buildroot}%{_sysconfdir}/pip.conf %files wheel %license LICENSE.txt %dir %{python_wheeldir}/ -%{python_wheeldir}/%{python_wheelname} %changelog +* Wed Nov 05 2025 Yu Peng - 25.3-1 +- Upgrade to 25.3 + * Remove support for the legacy setup.py develop editable method in setuptools editable installs + * Remove the deprecated --global-option and --build-option. --config-setting is now the only way to pass options to the build backend. + * Deprecate the PIP_CONSTRAINT environment variable for specifying build constraints. + * Permit spaces between a filepath and extras in an install requirement. + * Ensure the self-check files in the cache have the same permissions as the rest of the cache. + * Wed Sep 10 2025 xiangyuning - 23.3.1-5 - Fix CVE-2025-50181 diff --git a/remove-existing-dist-only-if-path-conflicts.patch b/remove-existing-dist-only-if-path-conflicts.patch deleted file mode 100644 index 3a9ea2590ad04677710c92d1494df0a0a6d66c76..0000000000000000000000000000000000000000 --- a/remove-existing-dist-only-if-path-conflicts.patch +++ /dev/null @@ -1,115 +0,0 @@ -From 2c3f3a590ddfc151a456b44a5f96f0f603d178e9 Mon Sep 17 00:00:00 2001 -From: Lumir Balhar -Date: Wed, 16 Feb 2022 08:36:21 +0100 -Subject: [PATCH] Prevent removing of the system packages installed under - /usr/lib when pip install --upgrade is executed. -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Resolves: rhbz#1550368 - -Co-Authored-By: Michal Cyprian -Co-Authored-By: Victor Stinner -Co-Authored-By: Petr Viktorin -Co-Authored-By: Lumir Balhar -Co-Authored-By: Miro HronĨok -Co-Authored-By: Karolina Surma ---- - src/pip/_internal/metadata/base.py | 12 +++++++++++- - src/pip/_internal/req/req_install.py | 2 +- - src/pip/_internal/resolution/legacy/resolver.py | 4 +++- - src/pip/_internal/resolution/resolvelib/factory.py | 12 ++++++++++++ - 4 files changed, 27 insertions(+), 3 deletions(-) - -diff --git a/src/pip/_internal/metadata/base.py b/src/pip/_internal/metadata/base.py -index 151fd6d..f9109cd 100644 ---- a/src/pip/_internal/metadata/base.py -+++ b/src/pip/_internal/metadata/base.py -@@ -28,7 +28,7 @@ from pip._vendor.packaging.utils import NormalizedName - from pip._vendor.packaging.version import LegacyVersion, Version - - from pip._internal.exceptions import NoneMetadataError --from pip._internal.locations import site_packages, user_site -+from pip._internal.locations import get_scheme, site_packages, user_site - from pip._internal.models.direct_url import ( - DIRECT_URL_METADATA_NAME, - DirectUrl, -@@ -560,6 +560,16 @@ class BaseDistribution(Protocol): - for extra in self._iter_egg_info_extras(): - metadata["Provides-Extra"] = extra - -+ @property -+ def in_install_path(self) -> bool: -+ """ -+ Return True if given Distribution is installed in -+ path matching distutils_scheme layout. -+ """ -+ norm_path = normalize_path(self.installed_location) -+ return norm_path.startswith(normalize_path( -+ get_scheme("").purelib.split('python')[0])) -+ - - class BaseEnvironment: - """An environment containing distributions to introspect.""" -diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py -index a1e376c..ed7facf 100644 ---- a/src/pip/_internal/req/req_install.py -+++ b/src/pip/_internal/req/req_install.py -@@ -416,7 +416,7 @@ class InstallRequirement: - f"lack sys.path precedence to {existing_dist.raw_name} " - f"in {existing_dist.location}" - ) -- else: -+ elif existing_dist.in_install_path: - self.should_reinstall = True - else: - if self.editable: -diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py -index fb49d41..040f2c1 100644 ---- a/src/pip/_internal/resolution/legacy/resolver.py -+++ b/src/pip/_internal/resolution/legacy/resolver.py -@@ -325,7 +325,9 @@ class Resolver(BaseResolver): - """ - # Don't uninstall the conflict if doing a user install and the - # conflict is not a user install. -- if not self.use_user_site or req.satisfied_by.in_usersite: -+ if ((not self.use_user_site -+ or req.satisfied_by.in_usersite) -+ and req.satisfied_by.in_install_path): - req.should_reinstall = True - req.satisfied_by = None - -diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py -index a4c24b5..e7e2da9 100644 ---- a/src/pip/_internal/resolution/resolvelib/factory.py -+++ b/src/pip/_internal/resolution/resolvelib/factory.py -@@ -1,6 +1,8 @@ - import contextlib - import functools - import logging -+import sys -+import sysconfig - from typing import ( - TYPE_CHECKING, - Dict, -@@ -549,6 +551,16 @@ class Factory: - if dist is None: # Not installed, no uninstallation required. - return None - -+ # Prevent uninstalling packages from /usr -+ try: -+ if dist.installed_location in ( -+ sysconfig.get_path('purelib', scheme='posix_prefix', vars={'base': sys.base_prefix}), -+ sysconfig.get_path('platlib', scheme='posix_prefix', vars={'platbase': sys.base_prefix}), -+ ): -+ return None -+ except KeyError: # this Python doesn't have 'rpm_prefix' scheme yet -+ pass -+ - # We're installing into global site. The current installation must - # be uninstalled, no matter it's in global or user site, because the - # user site installation has precedence over global. --- -2.35.3 -