Dulwich

raw JSON →
1.1.0 verified Tue May 12 auth: no python install: verified quickstart: stale

Dulwich is a pure-Python implementation of Git, providing an interface to both local and remote Git repositories without relying on the `git` executable or native code like `pygit2`. It offers both lower-level "plumbing" and higher-level "porcelain" APIs for interacting with Git objects and operations. The current version is 1.1.0, and it generally releases new versions every few weeks to months.

pip install dulwich
error ModuleNotFoundError: No module named 'dulwich'
cause The 'dulwich' library is not installed in the Python environment where the code is being executed.
fix
Run pip install dulwich in your terminal or ensure it's included in your project's requirements.txt and installed within your virtual environment.
error dulwich.client.HTTPUnauthorized: No valid credentials provided.
cause Authentication failed when attempting to access a remote Git repository over HTTP/HTTPS, likely due to incorrect or missing username, password, or access token.
fix
Provide valid credentials, either by embedding them in the URL (e.g., https://username:password@example.com/repo.git) or by passing username and password (or auth_info for more complex authentication) directly to dulwich.porcelain.clone or dulwich.porcelain.push.
error KeyError: b'HEAD'
cause This error typically occurs when `dulwich` attempts to read the 'HEAD' reference in a Git repository, but the repository is empty, corrupted, or not properly initialized, thus 'HEAD' does not exist or points to an invalid object.
fix
Ensure the repository has been initialized with dulwich.porcelain.init() and has at least one commit. If it's an existing repository, check its integrity and ensure the .git/HEAD file and other references are valid.
error TypeError: __init__() got an unexpected keyword argument 'username'.
cause You are passing 'username' and 'password' as direct keyword arguments to a lower-level `dulwich.client` constructor (e.g., `HttpGitClient.__init__`) which does not accept them directly, especially in older versions or when bypassing the recommended `dulwich.porcelain` functions.
fix
Use the higher-level dulwich.porcelain.clone or dulwich.porcelain.push functions, which are designed to handle username and password keyword arguments correctly for remote operations, or embed the credentials directly in the remote URL string.
breaking Dulwich 1.0.0 removed several deprecated functions. Code relying on these removed functions will break.
fix Refer to the Dulwich documentation for the 1.0.0 release notes and replace usage of deprecated functions with their modern equivalents.
breaking Version 0.25.0 introduced significant changes to public APIs, particularly the `dulwich.porcelain` module which was reorganized into submodules (e.g., `dulwich.porcelain.tags`, `dulwich.porcelain.notes`). While the main `dulwich.porcelain` module re-exports functions for backward compatibility, direct imports from old submodule paths or reliance on the previous internal structure may break in future releases, especially after 1.0.0.
fix Review calls to `dulwich.porcelain` functions. Ensure compatibility with the current API by referring to the official documentation. If directly importing from `dulwich.porcelain.*` submodules, verify the paths are still valid or use the top-level `dulwich.porcelain` module for re-exported functions.
gotcha Dulwich requires Python 3.10 or newer for recent versions. Running with older Python 3 versions (e.g., 3.9 or earlier) will lead to compatibility issues or errors.
fix Ensure your Python environment is running Python 3.10 or a newer compatible version. Check `python --version`.
gotcha While Dulwich is a pure-Python Git implementation, its performance for low-level operations can be significantly improved by installing with optional Rust bindings (formerly C extensions). Without these, operations might be noticeably slower.
fix For optimal performance, install Dulwich without the `--no-binary` option, allowing the optional Rust extensions to be built (requires a Rust toolchain). If using `--no-binary dulwich`, ensure `--config-settings "--build-option=--pure"` is *not* used unless slower performance is acceptable.
pip install --no-binary dulwich dulwich --config-settings "--build-option=--pure"
python os / libc variant status wheel install import disk
3.10 alpine (musl) --no-binary - - 0.11s 24.0M
3.10 alpine (musl) dulwich - - 0.11s 24.0M
3.10 slim (glibc) --no-binary - - 0.12s 24M
3.10 slim (glibc) dulwich - - 0.07s 26M
3.11 alpine (musl) --no-binary - - 0.17s 27.4M
3.11 alpine (musl) dulwich - - 0.16s 27.4M
3.11 slim (glibc) --no-binary - - 0.13s 28M
3.11 slim (glibc) dulwich - - 0.14s 30M
3.12 alpine (musl) --no-binary - - 0.13s 18.5M
3.12 alpine (musl) dulwich - - 0.13s 18.5M
3.12 slim (glibc) --no-binary - - 0.14s 19M
3.12 slim (glibc) dulwich - - 0.13s 21M
3.13 alpine (musl) --no-binary - - 0.17s 18.1M
3.13 alpine (musl) dulwich - - 0.13s 18.1M
3.13 slim (glibc) --no-binary - - 0.12s 19M
3.13 slim (glibc) dulwich - - 0.13s 20M
3.9 alpine (musl) --no-binary - - 0.10s 22.7M
3.9 alpine (musl) dulwich - - 0.10s 22.7M
3.9 slim (glibc) --no-binary - - 0.08s 23M
3.9 slim (glibc) dulwich - - 0.08s 25M

This quickstart demonstrates how to initialize a new Git repository, add a file, commit the changes using Dulwich's high-level 'porcelain' API, and then retrieve the latest commit message using its lower-level 'plumbing' API. It uses a temporary directory for a clean example.

import os
from dulwich.repo import Repo
from dulwich import porcelain
from tempfile import TemporaryDirectory

# Create a temporary directory for the repository
with TemporaryDirectory() as temp_dir:
    repo_path = os.path.join(temp_dir, 'my_repo')

    # Initialize a new repository
    repo = porcelain.init(repo_path)
    print(f"Initialized repository at: {repo_path}")

    # Create a file
    file_path = os.path.join(repo_path, 'README.md')
    with open(file_path, 'w') as f:
        f.write('# My Dulwich Repo\n')
    print(f"Created file: {file_path}")

    # Add the file to the index and commit
    porcelain.add(repo_path, ['README.md'])
    porcelain.commit(repo_path, message=b'Initial commit: Add README')
    print("Committed initial README.md")

    # Log the commit
    for entry in porcelain.log(repo_path, max_entries=1):
        print(f"Latest commit: {entry.commit.message.decode().strip()}")

    # Example of low-level (plumbing) API to get commit message
    low_level_repo = Repo(repo_path)
    head_id = low_level_repo.head()
    latest_commit = low_level_repo[head_id]
    print(f"Low-level API: Latest commit message: {latest_commit.message.decode().strip()}")