{"id":"1b6b9593776bd08c","repo":"pypa/pip","slug":"sorry-url-r-is-a-malformed-vcs-url-the-format","errorCode":null,"errorMessage":"Sorry, {url!r} is a malformed VCS url. The format is <vcs>+<protocol>://<url>, e.g. svn+http://myrepo/svn/MyApp#egg=MyApp","messagePattern":"Sorry, (.+?) is a malformed VCS url\\. The format is <vcs>\\+<protocol>://<url>, e\\.g\\. svn\\+http://myrepo/svn/MyApp#egg=MyApp","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/pip/_internal/vcs/versioncontrol.py","lineNumber":383,"sourceCode":"        information can be provided via the --username and --password options\n        instead of through the URL.  For other subclasses like Git without\n        such an option, auth information must stay in the URL.\n\n        Returns: (netloc, (username, password)).\n        \"\"\"\n        return netloc, (None, None)\n\n    @classmethod\n    def get_url_rev_and_auth(cls, url: str) -> tuple[str, str | None, AuthInfo]:\n        \"\"\"\n        Parse the repository URL to use, and return the URL, revision,\n        and auth info to use.\n\n        Returns: (url, rev, (username, password)).\n        \"\"\"\n        scheme, netloc, path, query, frag = urllib.parse.urlsplit(url)\n        if \"+\" not in scheme:\n            raise ValueError(\n                f\"Sorry, {url!r} is a malformed VCS url. \"\n                \"The format is <vcs>+<protocol>://<url>, \"\n                \"e.g. svn+http://myrepo/svn/MyApp#egg=MyApp\"\n            )\n        # Remove the vcs prefix.\n        scheme = scheme.split(\"+\", 1)[1]\n        netloc, user_pass = cls.get_netloc_and_auth(netloc, scheme)\n        rev = None\n        if \"@\" in path:\n            path, rev = path.rsplit(\"@\", 1)\n            if not rev:\n                raise InstallationError(\n                    f\"The URL {url!r} has an empty revision (after @) \"\n                    \"which is not supported. Include a revision after @ \"\n                    \"or remove @ from the URL.\"\n                )\n            rev = urllib.parse.unquote(rev)\n        url = urllib.parse.urlunsplit((scheme, netloc, path, query, \"\"))","sourceCodeStart":365,"sourceCodeEnd":401,"githubUrl":"https://github.com/pypa/pip/blob/d7d0d0a39494e28ec1c407bd0680e4a4d1067791/src/pip/_internal/vcs/versioncontrol.py#L365-L401","documentation":"Raised by `VCSTool.get_url_rev_and_auth` when a URL passed to pip as a VCS requirement has no `+` in its URL scheme. pip requires the explicit `<vcs>+<protocol>://` form (e.g. `svn+https://`, `git+ssh://`) so it can identify which backend to dispatch to. A bare `https://...` or `svn://...` scheme is ambiguous and is rejected before any network operation.","triggerScenarios":"Pip resolves a requirement link whose `urlsplit(url).scheme` contains no `+` character — e.g. `svn://host/repo`, `git://host/repo`, or a plain `https://github.com/...` URL routed through the VCS path. Triggered during `get_url_rev_and_auth(url)` at the `if '+' not in scheme:` check.","commonSituations":"Copying a repo browse URL from a browser instead of the VCS install form; misconfiguring `requirements.txt` or `dependency_links`; forgetting the `git+`/`svn+`/`hg+`/`bzr+` prefix; using `svn://` or `git://` protocols directly which lack a `+<protocol>` separator.","solutions":["Rewrite the URL to the `<vcs>+<protocol>://<host>/<path>` form, e.g. `git+https://github.com/org/repo.git`.","Add the VCS prefix matching the protocol you actually use: `git+ssh://`, `svn+https://`, `hg+https://`, `bzr+http://`.","If installing from a plain tarball/wheel, don't route it through VCS handling — give pip the direct archive URL instead."],"exampleFix":"# before\npip install svn://myrepo/svn/MyApp\n\n# after\npip install svn+https://myrepo/svn/MyApp#egg=MyApp","handlingStrategy":"validation","validationCode":"import urllib.parse\n\ndef is_valid_vcs_url(url: str) -> bool:\n    scheme = urllib.parse.urlsplit(url).scheme\n    return \"+\" in scheme and scheme.split(\"+\", 1)[0] in {\"git\", \"svn\", \"hg\", \"bzr\"}\n\n# before pip install:\nassert is_valid_vcs_url(req_url), f\"{req_url!r} is not a valid VCS URL\"","typeGuard":"def is_vcs_url(url: str) -> bool:\n    s = urllib.parse.urlsplit(url).scheme\n    return bool(s) and \"+\" in s","tryCatchPattern":"null","preventionTips":["Always write VCS install URLs as `<vcs>+<protocol>://...`.","Lint requirements files for the `<vcs>+` prefix before committing.","Generate VCS URLs from a helper that enforces the `+` scheme."],"tags":["vcs","url-parsing","requirements","configuration"],"analyzedSha":"d7d0d0a39494e28ec1c407bd0680e4a4d1067791","analyzedAt":"2026-08-04T20:55:04.259Z","schemaVersion":2}