Python bindings for libgit2 providing comprehensive Git repository operations and version control functionality.
—
Git index (staging area) operations for preparing commits. The index serves as a staging area between the working directory and repository, allowing fine-grained control over what changes are included in commits and providing conflict resolution capabilities.
The Index class provides access to the Git staging area with methods for adding, removing, and querying staged content.
class Index:
def read(self, force: bool = True):
"""Read index from disk"""
def write(self):
"""Write index to disk"""
def write_tree(self, repo: Repository = None) -> Oid:
"""
Write index as tree object.
Parameters:
- repo: Repository (uses index's repo if None)
Returns:
Tree object ID
"""
def read_tree(self, tree: Tree):
"""Read tree into index"""
def clear(self):
"""Remove all entries from index"""
def __len__(self) -> int:
"""Number of entries in index"""
def __getitem__(self, key: str | int) -> IndexEntry:
"""Get entry by path or index"""
def __contains__(self, path: str) -> bool:
"""Check if path is in index"""
def __iter__(self):
"""Iterate over index entries"""
# File Operations
def add(self, path: str):
"""Add file to index"""
def add_all(self, pathspecs: list[str] = None, flags: int = 0):
"""
Add all matching files to index.
Parameters:
- pathspecs: Path patterns (None = all files)
- flags: Add flags
"""
def remove(self, path: str, stage: int = 0):
"""Remove file from index"""
def remove_all(self, pathspecs: list[str], flags: int = 0):
"""Remove all matching files from index"""
def update_all(self, pathspecs: list[str] = None, flags: int = 0):
"""Update all matching files in index"""
# Entry Management
def get_entry_stage(self, path: str, stage: int) -> IndexEntry | None:
"""Get entry at specific stage"""
def add_frombuffer(self, path: str, buffer: bytes, mode: int):
"""Add file from buffer"""
# Conflict Resolution
@property
def conflicts(self) -> ConflictCollection:
"""Access merge conflicts"""
def conflict_cleanup(self):
"""Remove conflict markers from index"""
# Status
def diff_to_workdir(self, **kwargs) -> Diff:
"""Compare index to working directory"""
def diff_to_tree(self, tree: Tree, **kwargs) -> Diff:
"""Compare index to tree"""
# Index Add Flags
GIT_INDEX_ADD_DEFAULT: int # Default behavior
GIT_INDEX_ADD_FORCE: int # Add ignored files
GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH: int # Disable pathspec matching
GIT_INDEX_ADD_CHECK_PATHSPEC: int # Check pathspec validityIndexEntry represents a single file in the Git index with metadata.
class IndexEntry:
@property
def path(self) -> str:
"""File path relative to repository root"""
@property
def oid(self) -> Oid:
"""Object ID of file content"""
@property
def mode(self) -> int:
"""File mode (permissions)"""
@property
def flags(self) -> int:
"""Entry flags"""
@property
def flags_extended(self) -> int:
"""Extended entry flags"""
@property
def stage(self) -> int:
"""Merge stage (0=normal, 1=base, 2=ours, 3=theirs)"""
@property
def ctime(self) -> int:
"""Creation time"""
@property
def mtime(self) -> int:
"""Modification time"""
@property
def dev(self) -> int:
"""Device ID"""
@property
def ino(self) -> int:
"""Inode number"""
@property
def uid(self) -> int:
"""User ID"""
@property
def gid(self) -> int:
"""Group ID"""
@property
def file_size(self) -> int:
"""File size in bytes"""Handle merge conflicts in the index with three-way merge information.
class ConflictCollection:
def __getitem__(self, path: str) -> tuple[IndexEntry, IndexEntry, IndexEntry]:
"""Get conflict entries (ancestor, ours, theirs)"""
def __contains__(self, path: str) -> bool:
"""Check if path has conflicts"""
def __iter__(self):
"""Iterate over conflicted paths"""
def __len__(self) -> int:
"""Number of conflicted files"""
def get(self, path: str) -> tuple[IndexEntry, IndexEntry, IndexEntry] | None:
"""Get conflict entries with None fallback"""
class MergeFileResult:
@property
def automergeable(self) -> bool:
"""True if merge was automatic"""
@property
def path(self) -> str:
"""Merged file path"""
@property
def mode(self) -> int:
"""Merged file mode"""
@property
def contents(self) -> bytes:
"""Merged file contents"""File status flags used throughout pygit2 for working directory and index state.
# Index Status Flags
GIT_STATUS_CURRENT: int # No changes
GIT_STATUS_INDEX_NEW: int # New file in index
GIT_STATUS_INDEX_MODIFIED: int # Modified file in index
GIT_STATUS_INDEX_DELETED: int # Deleted file in index
GIT_STATUS_INDEX_RENAMED: int # Renamed file in index
GIT_STATUS_INDEX_TYPECHANGE: int # Type change in index
# Working Tree Status Flags
GIT_STATUS_WT_NEW: int # New file in workdir
GIT_STATUS_WT_MODIFIED: int # Modified file in workdir
GIT_STATUS_WT_DELETED: int # Deleted file in workdir
GIT_STATUS_WT_TYPECHANGE: int # Type change in workdir
GIT_STATUS_WT_RENAMED: int # Renamed file in workdir
GIT_STATUS_WT_UNREADABLE: int # Unreadable file
# Combined Status Flags
GIT_STATUS_IGNORED: int # Ignored file
GIT_STATUS_CONFLICTED: int # Conflicted fileimport pygit2
repo = pygit2.Repository('/path/to/repo')
index = repo.index
# Check index status
print(f"Index has {len(index)} entries")
# Add single file
index.add('README.md')
index.write()
# Add all files
index.add_all()
index.write()
# Remove file
index.remove('unwanted.txt')
index.write()
# Stage specific files
pathspecs = ['*.py', 'docs/*.md']
index.add_all(pathspecs)
index.write()# Examine index contents
for entry in index:
print(f"{entry.path}: {entry.oid} (mode: {oct(entry.mode)})")
if entry.stage > 0:
print(f" Merge stage: {entry.stage}")
# Get specific entry
if 'main.py' in index:
entry = index['main.py']
print(f"main.py: {entry.oid}")
print(f"Size: {entry.file_size} bytes")
print(f"Modified: {entry.mtime}")
# Check for conflicts
if index.conflicts:
print(f"Index has {len(index.conflicts)} conflicted files")# Create tree from index
tree_oid = index.write_tree()
# Create commit
signature = pygit2.Signature('Author', 'author@example.com')
commit_oid = repo.create_commit(
'HEAD',
signature,
signature,
'Commit message',
tree_oid,
[repo.head.target] # Parent commits
)
print(f"Created commit: {commit_oid}")# Check for merge conflicts
if repo.index.conflicts:
print("Resolving conflicts:")
for path in repo.index.conflicts:
ancestor, ours, theirs = repo.index.conflicts[path]
print(f"\nConflict in {path}:")
if ancestor:
print(f" Ancestor: {ancestor.oid}")
if ours:
print(f" Ours: {ours.oid}")
if theirs:
print(f" Theirs: {theirs.oid}")
# Get file contents for manual merge
if ours and theirs:
our_content = repo[ours.oid].data
their_content = repo[theirs.oid].data
# Perform manual merge (simplified)
merged_content = merge_files(our_content, their_content)
# Stage resolved content
repo.index.add_frombuffer(
path,
merged_content,
pygit2.GIT_FILEMODE_BLOB
)
# Write resolved index
repo.index.write()
# Clean up conflict markers
repo.index.conflict_cleanup()# Compare index to working directory
diff = repo.index.diff_to_workdir()
print(f"Working directory has {len(diff.deltas)} changes:")
for delta in diff.deltas:
status_map = {
pygit2.GIT_DELTA_ADDED: "Added",
pygit2.GIT_DELTA_DELETED: "Deleted",
pygit2.GIT_DELTA_MODIFIED: "Modified",
pygit2.GIT_DELTA_RENAMED: "Renamed"
}
status = status_map.get(delta.status, "Unknown")
print(f" {status}: {delta.new_file.path}")
# Compare index to HEAD
head_tree = repo[repo.head.target].tree
diff = repo.index.diff_to_tree(head_tree)
print(f"Index has {len(diff.deltas)} staged changes")# Add files from buffer
file_content = b"print('Hello, World!')"
index.add_frombuffer('hello.py', file_content, pygit2.GIT_FILEMODE_BLOB)
# Update only modified files (don't add new files)
index.update_all()
# Force add ignored files
index.add_all(flags=pygit2.GIT_INDEX_ADD_FORCE)
# Read specific tree into index
tree = repo.revparse_single('v1.0.0').tree
index.read_tree(tree)
index.write()
# Clear index completely
index.clear()
index.write()Install with Tessl CLI
npx tessl i tessl/pypi-pygit2