diff --git a/news/13407.bugfix.rst b/news/13407.bugfix.rst new file mode 100644 index 00000000000..0b3d7721306 --- /dev/null +++ b/news/13407.bugfix.rst @@ -0,0 +1,3 @@ +Modified to percent-encode the revision part in version control URLs. +For example, use ``git+https://example.com/repo.git@issue%231`` to specify the branch ``issue#1``. +If you previously used a branch name containing a ``%`` character in a version control URL, you now need to replace it with ``%25`` to ensure correct percent-encoding. diff --git a/src/pip/_internal/vcs/versioncontrol.py b/src/pip/_internal/vcs/versioncontrol.py index 4e91ccd4c4c..95cefb49e24 100644 --- a/src/pip/_internal/vcs/versioncontrol.py +++ b/src/pip/_internal/vcs/versioncontrol.py @@ -62,8 +62,9 @@ def make_vcs_requirement_url( repo_url: the remote VCS url, with any needed VCS prefix (e.g. "git+"). project_name: the (unescaped) project name. """ + quoted_rev = urllib.parse.quote(rev, "/") egg_project_name = project_name.replace("-", "_") - req = f"{repo_url}@{rev}#egg={egg_project_name}" + req = f"{repo_url}@{quoted_rev}#egg={egg_project_name}" if subdir: req += f"&subdirectory={subdir}" @@ -397,6 +398,7 @@ def get_url_rev_and_auth(cls, url: str) -> tuple[str, str | None, AuthInfo]: "which is not supported. Include a revision after @ " "or remove @ from the URL." ) + rev = urllib.parse.unquote(rev) url = urllib.parse.urlunsplit((scheme, netloc, path, query, "")) return url, rev, user_pass diff --git a/tests/unit/test_vcs.py b/tests/unit/test_vcs.py index 93f68fd7b72..73de6de832f 100644 --- a/tests/unit/test_vcs.py +++ b/tests/unit/test_vcs.py @@ -52,6 +52,11 @@ def test_ensure_svn_available() -> None: ("git+https://example.com/pkg", "dev", "zope-interface"), "git+https://example.com/pkg@dev#egg=zope_interface", ), + # Test a revision with special characters. + ( + ("git+https://example.com/pkg", "dev@1#2", "myproj"), + "git+https://example.com/pkg@dev%401%232#egg=myproj", + ), ], ) def test_make_vcs_requirement_url(args: tuple[Any, ...], expected: str) -> None: @@ -394,6 +399,11 @@ def test_git__get_url_rev__idempotent() -> None: "svn+https://svn.example.com/My+Project", ("https://svn.example.com/My+Project", None, (None, None)), ), + # Test percent-encoded characters in revision. + ( + "svn+https://svn.example.com/MyProject@dev%401%232", + ("https://svn.example.com/MyProject", "dev@1#2", (None, None)), + ), ], ) def test_version_control__get_url_rev_and_auth(