VCS, Direct URL & Local Project Installs

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the Python Reference bibliography.

Not every requirement comes from an index. pip can install straight from a version-control repository, from an archive URL, or from a directory on disk, either as a normal install or in editable mode. This page was written against pip 26.2.2 and run on CPython 3.14.6 on 2026-10-22. The VCS examples use a local Git repository, birdwatch-mono, which holds birdtally under libs/birdtally and has a tag v0.1.0; it is addressed with git+file:// so that the examples need no network. Scratch paths are shortened to …. How these installs are recorded is defined by the direct URL specification (PEP 610), introduced on Packaging Concepts & Standards.

VCS installs

A VCS requirement is a direct reference with a <vcs>+<scheme> URL:

birdtally @ git+https://git.example.org/birdwatch/birdwatch-mono.git@v0.1.0#subdirectory=libs/birdtally
birdtally @ git+ssh://git@git.example.org/birdwatch/birdwatch-mono.git@main
birdtally @ git+file:///srv/git/birdwatch-mono@053e5ca7de7521c66de7bfebb6458f986012e4b8
VCS Schemes

Git

git+https, git+ssh, git+file, git+http, git+git, git. The documentation discourages git, git+git (no authentication) and git+http (no TLS).

Mercurial

hg+file, hg+http, hg+https, hg+ssh, hg+static-http.

Subversion

svn, svn+svn, svn+http, svn+https, svn+ssh.

Bazaar

bzr+http, bzr+https, bzr+ssh, bzr+sftp, bzr+ftp, bzr+lp.

  • The ref after @ is a branch, a tag, a commit hash, or for Git any ref such as refs/pull/123/head. Give a full commit hash when you pin one: it lets pip skip network calls.

  • #subdirectory=<path> points at the project inside the repository, for monorepos.

  • Partial clones. With Git 2.17 or later pip asks for a partial clone (--filter=blob:none). If a server claims to support it but does not, PIP_NO_PARTIAL_CLONE_FOR_BROKEN_GIT_SERVER=1 (honoured since pip 26.2) forces a full clone. Since 26.2 pip also avoids re-fetching a pinned commit that is already present locally.

Installing birdtally from the tag:

python -m pip install --no-deps "birdtally @ git+file:///…/birdwatch-mono@v0.1.0#subdirectory=libs/birdtally"
Collecting birdtally @ git+file:/…/birdwatch-mono@v0.1.0#subdirectory=libs/birdtally
  Cloning file:///…/birdwatch-mono (to revision v0.1.0) to /tmp/pip-install-l2u21f9j/birdtally_70ee860d10004263b02d608981e09a5c
  Running command git clone --filter=blob:none --quiet file:///…/birdwatch-mono /tmp/pip-install-l2u21f9j/birdtally_70ee860d10004263b02d608981e09a5c
  Resolved file:///…/birdwatch-mono to commit 053e5ca7de7521c66de7bfebb6458f986012e4b8
…
Successfully installed birdtally-0.1.0

pip resolved the tag to a commit, built a wheel from that checkout and installed it. The commit is recorded in direct_url.json (the commit id is shortened here):

{
    "subdirectory": "libs/birdtally",
    "url": "file:///…/birdwatch-mono",
    "vcs_info": {
        "commit_id": "053e5ca7…f986012e4b8",
        "requested_revision": "v0.1.0",
        "vcs": "git"
    }
}

and pip freeze therefore writes the immutable commit rather than the tag:

birdtally @ git+file:///…/birdwatch-mono@053e5ca7de7521c66de7bfebb6458f986012e4b8#subdirectory=libs/birdtally

A VCS requirement is installed only if no satisfactory version is installed yet: pip compares the version in the package’s metadata, not the commit, so a new commit with an unchanged version number needs --upgrade or --force-reinstall.

Editable VCS installs

With -e, pip clones the repository into <venv>/src/<name> (or <cwd>/src/<name> outside a venv; --src <dir> changes it) and installs it in editable mode, so you can work in the checkout. The project name must be given with the name @ URL form, so that pip knows the clone directory before it has the metadata:

python -m pip install -e "birdtally @ git+file:///…/birdwatch-mono@v0.1.0#subdirectory=libs/birdtally"
ls .venv/src .venv/src/birdtally
python -m pip freeze
Obtaining birdtally from git+file:/…/birdwatch-mono@v0.1.0#subdirectory=libs/birdtally
  Cloning file:///…/birdwatch-mono (to revision v0.1.0) to ./.venv/src/birdtally
  Resolved file:///…/birdwatch-mono to commit 053e5ca7de7521c66de7bfebb6458f986012e4b8
…
Successfully installed birdtally-0.1.0
.venv/src:
birdtally

.venv/src/birdtally:
libs
-e git+file:///…/birdwatch-mono@053e5ca7de7521c66de7bfebb6458f986012e4b8#egg=birdtally&subdirectory=libs/birdtally

pip freeze still writes the old #egg= fragment for editable VCS installs. The fragment is a legacy way to give the project name: the documentation discourages it in favour of the direct URL form, it must be a bare project name, and pip 26.0 removed support for anything else in it (such as #egg=birdtally[pretty] or a version).

Direct URLs to archives

A wheel or sdist on any web server can be installed with name @ https://…/file, optionally with a #sha256= fragment that pip checks. direct_url.json then records archive_info with the hash, and pip freeze reproduces the URL. PyPI rejects uploads whose own dependencies are direct references, so this form is for applications and lock files, not for library metadata.

Local archives and directories

python -m pip install ./dist/birdtally-0.1.0-py3-none-any.whl   # a wheel: unpacked, no build
python -m pip install ./dist/birdtally-0.1.0.tar.gz             # an sdist: built into a wheel first
python -m pip install ./birdtally                               # a project directory: built in place

A local directory must contain pyproject.toml or setup.py. Its direct_url.json has dir_info, and since pip 26.2 local directories are never served from or stored in the persistent wheel cache, so a rebuilt directory is always rebuilt.

Regular and editable installs of a local project

pip install . (regular) pip install -e . (editable)

What is installed

A wheel built from the directory: a copy of the files.

A small editable wheel (PEP 660) that points at the directory.

Editing the source

Needs a reinstall.

Takes effect on the next import (except metadata, entry points and compiled code).

Backend hooks

build_wheel

build_editable

direct_url.json

{"dir_info": {}, …}

{"dir_info": {"editable": true}, …}

The editable install of birdtally, with the .pth file hatchling uses, is shown on Packaging Concepts & Standards.

History that explains old instructions you may find:

  • pip 21.3 added PEP 660 editables and switched to in-tree builds: the project is no longer copied to a temporary directory before the backend runs (the --use-deprecated=out-of-tree-build fallback was removed in 22.1). Builds are faster, but backends such as setuptools leave artefacts (build/, *.egg-info) in your tree and may not support parallel builds of the same directory.

  • pip 25.3 removed the setup.py develop fallback for projects whose backend does not implement build_editable. With setuptools that means version 64 or later is required for pip install -e ..

pip install -e . ignores lock files. Installing the project resolves its [project] dependencies again and may upgrade or downgrade what a lock or requirements file installed. Install the locked dependencies first and then the project with --no-deps (pip install -r requirements.lock && pip install --no-deps -e .), or use Poetry, whose poetry install installs the root project from the lock.

pip and Poetry

pip Poetry

name @ git+https://…@v0.1.0

poetry add git+https://…#v0.1.0, or { git = "…", tag = "v0.1.0" }

#subdirectory=libs/birdtally

subdirectory = "libs/birdtally"

name @ https://…/file.whl

{ url = "https://…/file.whl" }

pip install ./birdtally

{ path = "../birdtally" }

pip install -e ./birdtally

{ path = "../birdtally", develop = true }

Poetry records the resolved commit in poetry.lock; see Poetry: Dependency Specification.