pypa/pip · error · InstallationError

The URL has an empty revision (after @) which is not…

Error message

The URL {url!r} has an empty revision (after @) which is not supported. Include a revision after @ or remove @ from the URL.

What it means

Raised by VersionControl.get_url_rev_and_auth() when the URL path contains an '@' separator but the revision string after it is empty. The '@' syntax specifies a VCS revision/commit; an empty revision is ambiguous and unsupported.

Solutions

  1. Remove the trailing '@' from the URL, or add a valid revision after it
  2. Specify a concrete revision: branch, tag, or commit hash after the '@'
  3. Validate requirement URLs in your requirements.txt for stray '@' characters

Example fix

// before
pip install "git+https://github.com/user/repo.git@"
// after
# either remove the @
pip install git+https://github.com/user/repo.git
# or specify a revision
pip install "git+https://github.com/user/repo.git@v1.2.3"
Defensive patterns

Strategy: validation

Validate before calling

import urllib.parse

def vcs_url_has_valid_revision(url: str) -> bool:
    path = urllib.parse.urlsplit(url).path
    if '@' not in path:
        return True
    _, _, rev = path.rpartition('@')
    return bool(rev)

# before installing
if not vcs_url_has_valid_revision(url):
    raise ValueError(f'URL has empty revision after @: {url}')

Type guard

def revision_is_nonempty(url_path: str) -> bool:
    if '@' not in url_path:
        return True
    return bool(url_path.rsplit('@', 1)[-1])

Try / catch

from pip._internal.exceptions import InstallationError

try:
    url, rev, auth = vc.get_url_rev_and_auth(url)
except InstallationError as e:
    if 'empty revision' in str(e):
        raise ConfigError(f'remove trailing @ or add a revision: {e}')
    raise

Prevention

When it happens

Trigger: Calling get_url_rev_and_auth(url) where path.rsplit('@', 1) yields an empty rev string. For example, 'git+https://example.com/repo.git@' — the '@' is present but nothing follows it.

Common situations: A typo leaving a trailing '@' in the URL. A requirements file with a URL like 'git+https://.../@' from a botched variable substitution. Copying a URL that had a revision deleted but the '@' left behind.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/1a226a5f44785e5c. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_internal/vcs/versioncontrol.py:395

        and auth info to use.

        Returns: (url, rev, (username, password)).
        """
        scheme, netloc, path, query, frag = urllib.parse.urlsplit(url)
        if "+" not in scheme:
            raise ValueError(
                f"Sorry, {url!r} is a malformed VCS url. "
                "The format is <vcs>+<protocol>://<url>, "
                "e.g. svn+http://myrepo/svn/MyApp#egg=MyApp"
            )
        # Remove the vcs prefix.
        scheme = scheme.split("+", 1)[1]
        netloc, user_pass = cls.get_netloc_and_auth(netloc, scheme)
        rev = None
        if "@" in path:
            path, rev = path.rsplit("@", 1)
            if not rev:
                raise InstallationError(
                    f"The URL {url!r} has an empty revision (after @) "
                    "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

    @staticmethod
    def make_rev_args(username: str | None, password: HiddenText | None) -> CommandArgs:
        """
        Return the RevOptions "extra arguments" to use in obtain().
        """
        return []

    def get_url_rev_options(self, url: HiddenText) -> tuple[HiddenText, RevOptions]:
        """
        Return the URL and RevOptions object to use in obtain(),

View on GitHub (pinned to f399c37189)