pygit2
pygit2 is a set of Python bindings for libgit2, a portable, pure C implementation of the Git core methods. It provides a low-level, plumbing-focused API for interacting with Git repositories. The library is actively maintained, with version 1.19.2 being the latest as of March 2026, and typically sees multiple minor releases per year, often in sync with libgit2 updates and bug fixes. It supports Python versions 3.11 through 3.14, and PyPy3 7.3+.
Warnings
- breaking `Odb.read(...)` now returns `enums.ObjectType` (an enum) instead of an integer for the object type. If your code expects an `int` for the object type when reading from the ODB, it will break.
- breaking The `IndexEntry.hex` property has been removed. Accessing it will raise an AttributeError.
- breaking Several repository methods related to remotes and submodules have been removed or deprecated in favor of accessing them through the `Repository.remotes` and `Repository.submodules` objects. E.g., `Repository.create_remote` was removed.
- gotcha Building pygit2 from source requires the `libgit2` development files to be installed on your system. If these are not found, installation will fail with `fatal error: git2.h: No such file or directory`. Binary wheels are available for many platforms, but not all (e.g., macOS often requires manual `libgit2` installation via Homebrew).
- gotcha Older versions of `pygit2` (e.g., v1.4.0, often found in older Linux distributions) might use `libgit2` builds that do not support modern SSH key types (e.g., RSA SHA-1 keys are deprecated by GitHub). This can lead to `GitError: ERROR: You're using an RSA key with SHA-1, which is no longer allowed` when pushing/pulling via SSH.
- gotcha When making an *initial* commit in a newly created repository, the first argument to `Repository.create_commit()` should typically be `'HEAD'` (or a specific branch name like `'refs/heads/main'`) instead of an uninitialized reference like `'refs/heads/master'` if the branch does not yet exist. Using an incorrect or non-existent ref might lead to errors or unexpected behavior if the head hasn't been established.
- gotcha The `pygit2` library versions often track the `libgit2` C library versions. Installing a `pygit2` version that is incompatible with your system's `libgit2` version (especially when building from source) can lead to compilation errors or runtime issues. For example, `pygit2 1.19.x` is designed for `libgit2 1.9.x`.
Install
-
pip install pygit2 -
brew install libgit2 # On macOS, then pip install pygit2 # For other OSes, install libgit2 development packages (e.g., libgit2-dev on Debian/Ubuntu, libgit2-devel on Fedora/RHEL) if binary wheel is not available.
Imports
- Repository
from pygit2 import Repository
- init_repository
from pygit2 import init_repository
- Signature
from pygit2 import Signature
- GitError
from pygit2 import GitError
- enums
from pygit2 import enums
Quickstart
import pygit2
import os
import shutil
import tempfile
from pygit2 import enums
def create_and_commit_repo():
# Create a temporary directory for the repository
repo_path = tempfile.mkdtemp()
print(f"Creating repository at: {repo_path}")
try:
# Initialize a new bare repository
# For a non-bare repo with a working directory, use `bare=False`
repo = pygit2.init_repository(repo_path, bare=False)
# Configure author and committer details
author = pygit2.Signature("Test User", "test@example.com")
committer = pygit2.Signature("Test User", "test@example.com")
# Create a file in the working directory
file_path = os.path.join(repo_path, "README.md")
with open(file_path, "w") as f:
f.write("# My New Repository\n\nThis is a test repository.")
# Add the file to the index
repo.index.add("README.md")
repo.index.write()
# Create a tree from the index
tree = repo.index.write_tree()
# Create the initial commit
# For an initial commit, the parents list is empty, and HEAD is used
commit_id = repo.create_commit(
'HEAD', # Reference to update
author, # Author signature
committer, # Committer signature
'Initial commit', # Commit message
tree, # Tree object for the commit
[] # Parent commits (empty for initial commit)
)
print(f"Initial commit created with ID: {commit_id}")
# Checkout the branch to populate the working directory
repo.checkout('refs/heads/main') # Or 'refs/heads/master' depending on default branch
print(f"Repository content in working directory: {os.listdir(repo_path)}")
finally:
# Clean up the temporary directory
shutil.rmtree(repo_path)
print(f"Cleaned up repository at: {repo_path}")
if __name__ == "__main__":
create_and_commit_repo()