Pure Python implementation of the Git version control system providing comprehensive access to Git repositories without requiring the Git command-line tool
—
Complete CLI implementation providing Git command equivalents with argument parsing, progress reporting, and consistent behavior.
Primary entry point function for the dulwich command-line interface.
def main(argv: List[str] = None) -> int:
"""
Main entry point for dulwich CLI.
Args:
argv: Command line arguments (defaults to sys.argv)
Returns:
Exit code (0 for success, non-zero for error)
"""
def signal_int(signal, frame) -> None:
"""
Signal handler for SIGINT (Ctrl+C).
Args:
signal: Signal number
frame: Current stack frame
"""
def signal_quit(signal, frame) -> None:
"""
Signal handler for SIGQUIT.
Args:
signal: Signal number
frame: Current stack frame
"""Base classes for implementing individual Git commands and command groups.
class Command:
"""Base class for dulwich CLI commands."""
common_options: List[Tuple[str, str]] # Common command-line options
def run(self, args: List[str]) -> int:
"""
Run the command with given arguments.
Args:
args: Command-line arguments
Returns:
Exit code
"""
def add_parser_arguments(self, parser) -> None:
"""Add command-specific arguments to argument parser."""
def setup_logging(self, args) -> None:
"""Setup logging based on verbosity arguments."""
class SuperCommand(Command):
"""
Base class for commands that group multiple subcommands.
Examples include 'git remote', 'git submodule', 'git stash', etc.
"""
subcommands: ClassVar[dict[str, type[Command]]]
def run_subcommand(self, subcommand: str, args: List[str]) -> int:
"""
Run a specific subcommand.
Args:
subcommand: Name of subcommand to run
args: Arguments for the subcommand
Returns:
Exit code
"""Helper functions used throughout the CLI implementation.
def parse_relative_time(time_str: str) -> int:
"""
Parse relative time string like '2 weeks ago' into seconds.
Args:
time_str: String like '2 weeks ago' or 'now'
Returns:
Number of seconds
Raises:
ValueError: If time string cannot be parsed
"""
def format_bytes(bytes: int) -> str:
"""
Format bytes as human-readable string.
Args:
bytes: Number of bytes
Returns:
Human-readable string like "1.5 MB"
"""
def launch_editor(template_content: bytes = b"") -> bytes:
"""
Launch editor for user to enter text.
Args:
template_content: Initial content for editor
Returns:
Edited content as bytes
"""Classes for handling paged output in CLI commands.
class Pager:
"""
Text output pager for long command output.
Automatically pipes output through system pager
(like 'less') when output is longer than terminal.
"""
def write(self, text: str) -> int:
"""
Write text to pager.
Args:
text: Text to write
Returns:
Number of characters written
"""
def flush(self) -> None:
"""Flush pager output."""
def close(self) -> None:
"""Close pager and wait for completion."""
class PagerBuffer:
"""
Binary buffer wrapper for Pager to mimic sys.stdout.buffer.
Allows CLI commands to write binary data through the pager
by converting to text with appropriate encoding.
"""
def __init__(self, pager: Pager) -> None:
"""
Initialize buffer wrapper.
Args:
pager: Pager instance to wrap
"""
def write(self, data: bytes) -> int:
"""
Write bytes to pager.
Args:
data: Bytes to write
Returns:
Number of bytes written
"""
def flush(self) -> None:
"""Flush pager output."""
def writelines(self, lines: list[bytes]) -> None:
"""
Write multiple lines to pager.
Args:
lines: List of byte lines to write
"""
def close(self) -> None:
"""Close pager."""The dulwich CLI provides the following commands equivalent to Git operations:
class cmd_init(Command):
"""Initialize a new Git repository."""
class cmd_clone(Command):
"""Clone a Git repository."""
class cmd_status(Command):
"""Show working tree status."""
class cmd_log(Command):
"""Show commit history."""
class cmd_show(Command):
"""Show object contents."""
class cmd_describe(Command):
"""Describe a commit using the most recent tag."""
class cmd_reflog(Command):
"""Show reference logs."""
class cmd_count_objects(Command):
"""Count unpacked objects and display statistics."""class cmd_add(Command):
"""Add files to the staging area."""
class cmd_rm(Command):
"""Remove files from repository."""
class cmd_mv(Command):
"""Move/rename files."""
class cmd_commit(Command):
"""Create a new commit."""
class cmd_reset(Command):
"""Reset repository state."""
class cmd_checkout(Command):
"""Checkout branches or files."""
class cmd_ls_files(Command):
"""Show information about files in the index and working tree."""
class cmd_check_ignore(Command):
"""Check if files are ignored by .gitignore rules."""
class cmd_check_mailmap(Command):
"""Check names and email addresses against mailmap."""class cmd_branch(Command):
"""List, create, or delete branches."""
class cmd_checkout(Command):
"""Switch branches or restore working tree files."""
class cmd_merge(Command):
"""Merge branches."""
class cmd_merge_tree(Command):
"""Show three-way merge without touching index."""
class cmd_cherry_pick(Command):
"""Apply changes from existing commits."""
class cmd_revert(Command):
"""Revert commits by creating new commits."""
class cmd_rebase(Command):
"""Reapply commits on top of another base tip."""class cmd_fetch(Command):
"""Fetch from remote repository."""
class cmd_fetch_pack(Command):
"""Fetch pack from remote repository (low-level)."""
class cmd_pull(Command):
"""Pull changes from remote repository."""
class cmd_push(Command):
"""Push changes to remote repository."""
class cmd_remote(SuperCommand):
"""Manage remote repositories."""
class cmd_remote_add(Command):
"""Add remote repository."""
class cmd_ls_remote(Command):
"""List remote references."""class cmd_tag(Command):
"""Create, list, or delete tags."""
class cmd_for_each_ref(Command):
"""Iterate over all references matching pattern."""class cmd_diff(Command):
"""Show differences between commits, trees, or files."""
class cmd_diff_tree(Command):
"""Show differences between trees."""
class cmd_format_patch(Command):
"""Generate patches in mbox format."""class cmd_stash(SuperCommand):
"""Stash working directory changes."""
class cmd_stash_list(Command):
"""List stash entries."""
class cmd_stash_push(Command):
"""Save local modifications to new stash entry."""
class cmd_stash_pop(Command):
"""Apply stash and remove from stash list."""
class cmd_archive(Command):
"""Create archive from repository."""
class cmd_gc(Command):
"""Cleanup and optimize repository."""
class cmd_fsck(Command):
"""Verify repository integrity."""
class cmd_pack_refs(Command):
"""Pack references for efficiency."""
class cmd_update_server_info(Command):
"""Update auxiliary server information."""
class cmd_prune(Command):
"""Prune unreachable objects from object database."""
class cmd_repack(Command):
"""Repack objects in repository."""
class cmd_filter_branch(Command):
"""Rewrite branches by applying filters."""
class cmd_bisect(SuperCommand):
"""Use binary search to find commit that introduced bug."""class cmd_rev_list(Command):
"""List commit objects in reverse chronological order."""
class cmd_ls_tree(Command):
"""List tree contents."""
class cmd_hash_object(Command):
"""Compute object ID and optionally store object."""
class cmd_symbolic_ref(Command):
"""Read and modify symbolic references."""
class cmd_write_tree(Command):
"""Create tree object from current index."""
class cmd_commit_tree(Command):
"""Create commit object."""
class cmd_pack_objects(Command):
"""Create packed archive of objects."""
class cmd_unpack_objects(Command):
"""Unpack objects from packed archive."""
class cmd_dump_pack(Command):
"""Dump contents of pack file."""
class cmd_dump_index(Command):
"""Dump contents of index file."""class cmd_daemon(Command):
"""Run Git protocol daemon."""
class cmd_web_daemon(Command):
"""Run web-based Git repository browser."""
class cmd_upload_pack(Command):
"""Send packed objects for fetch operations."""
class cmd_receive_pack(Command):
"""Receive packed objects for push operations."""class cmd_submodule(SuperCommand):
"""Initialize, update, or inspect submodules."""
class cmd_submodule_list(Command):
"""List submodules."""
class cmd_submodule_init(Command):
"""Initialize submodules."""
class cmd_submodule_add(Command):
"""Add new submodule."""
class cmd_submodule_update(Command):
"""Update existing submodules."""class cmd_notes(SuperCommand):
"""Add, show, or manipulate object notes."""
class cmd_notes_add(Command):
"""Add notes to objects."""
class cmd_notes_show(Command):
"""Show notes for objects."""
class cmd_notes_remove(Command):
"""Remove notes from objects."""
class cmd_notes_list(Command):
"""List notes objects."""class cmd_worktree(SuperCommand):
"""Manage multiple working trees."""
class cmd_worktree_add(Command):
"""Add new working tree."""
class cmd_worktree_list(Command):
"""List working trees."""
class cmd_worktree_remove(Command):
"""Remove working tree."""
class cmd_worktree_prune(Command):
"""Prune working tree information."""
class cmd_worktree_lock(Command):
"""Lock working tree."""
class cmd_worktree_unlock(Command):
"""Unlock working tree."""
class cmd_worktree_move(Command):
"""Move working tree."""class cmd_lfs(Command):
"""Git Large File Storage operations."""class cmd_bundle(Command):
"""Create, verify, list, and unbundle Git bundles."""class cmd_help(Command):
"""Show help information."""
class cmd_config(Command):
"""Get and set repository or global options."""
class cmd_annotate(Command):
"""Annotate file lines with commit information."""
class cmd_blame(Command):
"""Show what revision and author last modified each line."""class CommitMessageError(Exception):
"""Raised when there's an issue with commit message."""from dulwich.cli import main, parse_relative_time, format_bytes
import sys
# Run dulwich commands programmatically
exit_code = main(['status'])
if exit_code != 0:
print("Command failed", file=sys.stderr)
# Clone a repository
exit_code = main(['clone', 'https://github.com/user/repo.git', 'local-dir'])
# Show commit log with time filtering
since_time = parse_relative_time("2 weeks ago")
exit_code = main(['log', f'--since={since_time}', '--oneline'])
# Format repository size information
repo_size = 1073741824 # 1GB in bytes
print(f"Repository size: {format_bytes(repo_size)}")from dulwich.cli import Command, SuperCommand, launch_editor
from dulwich.repo import Repo
import argparse
class CustomCommand(Command):
"""Example custom command."""
def add_parser_arguments(self, parser):
parser.add_argument('--verbose', '-v', action='store_true',
help='Enable verbose output')
parser.add_argument('--edit', action='store_true',
help='Launch editor for input')
parser.add_argument('path', help='Repository path')
def run(self, args):
repo = Repo(args.path)
if args.verbose:
print(f"Repository at: {repo.path}")
if args.edit:
content = launch_editor(b"Enter your text here:\n")
print(f"You entered: {content.decode()}")
# Perform custom operation
refs = list(repo.refs.keys())
print(f"Found {len(refs)} references")
return 0
class CustomSuperCommand(SuperCommand):
"""Example super command with subcommands."""
subcommands = {
'list': CustomCommand,
'show': CustomCommand,
}
def run(self, args):
if not args or args[0] not in self.subcommands:
print("Available subcommands:", list(self.subcommands.keys()))
return 1
return self.run_subcommand(args[0], args[1:])# Basic repository operations
dulwich init my-repo
dulwich clone https://github.com/user/repo.git
dulwich status
dulwich add file.txt
dulwich commit -m "Add file"
# Branch operations
dulwich branch feature-branch
dulwich checkout feature-branch
dulwich merge main
dulwich cherry-pick abc123
dulwich revert def456
# Remote operations
dulwich remote add origin https://github.com/user/repo.git
dulwich push origin main
dulwich pull origin main
dulwich fetch origin
dulwich ls-remote origin
# Stashing operations
dulwich stash push -m "Save work"
dulwich stash list
dulwich stash pop
# Information commands
dulwich log --oneline -10
dulwich show HEAD
dulwich diff HEAD~1..HEAD
dulwich describe HEAD
dulwich blame file.txt
# Submodule operations
dulwich submodule add https://github.com/user/lib.git lib
dulwich submodule update
dulwich submodule list
# Notes operations
dulwich notes add -m "Important note" abc123
dulwich notes show abc123
dulwich notes list
# Worktree operations
dulwich worktree add ../feature-work feature-branch
dulwich worktree list
dulwich worktree remove ../feature-work
# Advanced operations
dulwich archive HEAD --format=tar.gz
dulwich gc --aggressive
dulwich fsck --full
dulwich filter-branch --tree-filter 'rm -f passwords.txt'
dulwich bisect start
dulwich bundle create backup.bundle HEAD
# Low-level operations
dulwich hash-object file.txt
dulwich ls-tree HEAD
dulwich pack-objects .git/objects/pack/pack
dulwich unpack-objects < pack-filefrom dulwich.cli import main
from dulwich.errors import NotGitRepository
try:
exit_code = main(['status'])
if exit_code != 0:
print("Command completed with errors")
except NotGitRepository:
print("Not in a Git repository")
except Exception as e:
print(f"Unexpected error: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-dulwich