Rustic Git
A Rust library for Git repository operations with a clean, type-safe API.
Overview
Rustic Git provides a simple, ergonomic interface for common Git operations. It follows a repository-centric design where you create a Repository
instance and call methods on it to perform Git operations.
Features
-Repository initialization and opening
-Enhanced file status checking with separate staged/unstaged tracking
-Precise Git state representation using IndexStatus and WorktreeStatus enums
-File staging (add files, add all, add updates)
-Commit creation with hash return
-Complete branch operations with type-safe Branch API
-Branch management (create, delete, checkout, list)
-Commit history & log operations with multi-level API
-Advanced commit querying with filtering and analysis
-Repository configuration management with type-safe API
-Remote management with full CRUD operations and network support
-Network operations (fetch, push, clone) with advanced options
-File lifecycle operations (restore, reset, remove, move, .gitignore management)
-Diff operations with multi-level API and comprehensive options
-Tag management with comprehensive operations and filtering
-Lightweight and annotated tags with type-safe API
-Stash operations with comprehensive stash management and filtering
-Advanced stash options (untracked files, keep index, specific paths)
-Reset operations with comprehensive soft/mixed/hard reset support
-Repository history management with type-safe ResetMode API
-Merge operations with comprehensive branch merging and conflict handling
-Advanced merge options (fast-forward control, merge strategies, conflict detection)
-Type-safe error handling with custom GitError enum
-Universal Hash
type for Git objects
-Immutable collections (Box<[T]>) for memory efficiency
-Const enum conversions with zero runtime cost
-Comprehensive test coverage (187+ tests)
Installation
Add this to your Cargo.toml
:
[]
= "*"
Or use cargo add
to automatically add the latest version:
Quick Start
use ;
API Documentation
Repository Lifecycle
Repository::init(path, bare) -> Result<Repository>
Initialize a new Git repository.
// Initialize a regular repository
let repo = init?;
// Initialize a bare repository
let bare_repo = init?;
Repository::open(path) -> Result<Repository>
Open an existing Git repository.
let repo = open?;
Status Operations
Repository::status() -> Result<GitStatus>
Get the current repository status with enhanced staged/unstaged file tracking.
let status = repo.status?;
// Check if repository is clean
if status.is_clean else
// Get files by staging state
let staged_files: = status.staged_files.collect;
let unstaged_files: = status.unstaged_files.collect;
let untracked_files: = status.untracked_entries.collect;
// Filter by specific status types
let modified_in_index: = status
.files_with_index_status
.collect;
let modified_in_worktree: = status
.files_with_worktree_status
.collect;
// Work with all file entries directly
for entry in status.entries
The GitStatus
struct contains:
entries: Box<[FileEntry]>
- Immutable collection of file entriesis_clean()
- Returns true if no changeshas_changes()
- Returns true if any changes existstaged_files()
- Iterator over files with index changes (staged)unstaged_files()
- Iterator over files with worktree changes (unstaged)untracked_entries()
- Iterator over untracked filesignored_files()
- Iterator over ignored filesfiles_with_index_status(status)
- Filter by specific index statusfiles_with_worktree_status(status)
- Filter by specific worktree status
File Status Types
The enhanced status API uses separate enums for index (staged) and worktree (unstaged) states:
// Index (staging area) status
// Worktree (working directory) status
// File entry combining both states
Both enums support const character conversion:
// Convert to/from git porcelain characters
let status = from_char; // IndexStatus::Modified
let char = status.to_char; // 'M'
// Display formatting
println!; // Prints: M
println!; // Prints: ?
Staging Operations
Repository::add(paths) -> Result<()>
Add specific files to the staging area.
// Add single file
repo.add?;
// Add multiple files
repo.add?;
// Add with Path objects
use Path;
repo.add?;
Repository::add_all() -> Result<()>
Add all changes to the staging area (equivalent to git add .
).
repo.add_all?;
Repository::add_update() -> Result<()>
Add all tracked files that have been modified (equivalent to git add -u
).
repo.add_update?;
Configuration Operations
Repository::config() -> RepoConfig
Get a configuration manager for the repository to set and get git configuration values.
// Configure git user (convenience method)
repo.config.set_user?;
// Get user configuration
let = repo.config.get_user?;
println!;
// Set any git configuration value
repo.config.set?;
repo.config.set?;
// Get any git configuration value
let autocrlf = repo.config.get?;
println!;
// Remove a configuration value
repo.config.unset?;
Configuration Methods
set_user(name, email)
- Convenience method to set both user.name and user.emailget_user()
- Get user configuration as a tuple (name, email)set(key, value)
- Set any git configuration valueget(key)
- Get any git configuration value as Stringunset(key)
- Remove a git configuration value
All configuration operations are scoped to the specific repository.
Remote Management
Repository::add_remote(name, url) -> Result<()>
Add a remote to the repository.
repo.add_remote?;
repo.add_remote?;
Repository::list_remotes() -> Result<RemoteList>
List all remotes with their URLs.
let remotes = repo.list_remotes?;
for remote in remotes.iter
// Find specific remote
if let Some = remotes.find
Repository::remove_remote(name) -> Result<()>
Remove a remote from the repository.
repo.remove_remote?;
Repository::rename_remote(old_name, new_name) -> Result<()>
Rename an existing remote.
repo.rename_remote?;
Repository::get_remote_url(name) -> Result<String>
Get the URL for a specific remote.
let url = repo.get_remote_url?;
println!;
Network Operations
Repository::fetch(remote) -> Result<()>
Fetch changes from a remote repository.
repo.fetch?;
Repository::fetch_with_options(remote, options) -> Result<()>
Fetch with advanced options.
let options = new
.with_prune // Remove stale remote-tracking branches
.with_tags // Fetch tags
.with_all_remotes; // Fetch from all remotes
repo.fetch_with_options?;
Repository::push(remote, branch) -> Result<()>
Push changes to a remote repository.
repo.push?;
Repository::push_with_options(remote, branch, options) -> Result<()>
Push with advanced options.
let options = new
.with_force // Force push (use with caution)
.with_tags // Push tags
.with_set_upstream; // Set upstream tracking
repo.push_with_options?;
Repository::clone(url, path) -> Result<Repository>
Clone a remote repository (static method).
let repo = clone?;
File Lifecycle Operations
Repository::checkout_file(path) -> Result<()>
Restore a file from HEAD, discarding local changes.
// Restore a modified file to its last committed state
repo.checkout_file?;
Repository::restore(paths, options) -> Result<()>
Restore files with advanced options using git's restore command.
// Restore from a specific commit
let options = new
.with_source
.with_worktree;
repo.restore?;
// Restore only staged changes
let staged_options = new.with_staged;
repo.restore?;
// Restore both staged and worktree
let both_options = new
.with_staged
.with_worktree;
repo.restore?;
Repository::reset_file(path) -> Result<()>
Unstage a file, removing it from the staging area while keeping changes in working directory.
// Unstage a previously staged file
repo.reset_file?;
Repository::rm(paths) -> Result<()>
Remove files from both working directory and repository.
// Remove files from repository
repo.rm?;
Repository::rm_with_options(paths, options) -> Result<()>
Remove files with advanced options.
// Remove from index only, keep in working tree
let cached_options = new.with_cached;
repo.rm_with_options?;
// Force remove with recursive option
let force_options = new
.with_force
.with_recursive;
repo.rm_with_options?;
// Remove with ignore-unmatch (don't fail if files don't exist)
let safe_options = new.with_ignore_unmatch;
repo.rm_with_options?;
Repository::mv(source, destination) -> Result<()>
Move or rename files and directories.
// Rename a file
repo.mv?;
// Move to different directory
repo.mv?;
Repository::mv_with_options(source, destination, options) -> Result<()>
Move files with advanced options.
// Force move even if destination exists
let force_options = new.with_force;
repo.mv_with_options?;
// Dry run to see what would be moved
let dry_run_options = new
.with_dry_run
.with_verbose;
repo.mv_with_options?;
Repository::ignore_add(patterns) -> Result<()>
Add patterns to .gitignore file.
// Add ignore patterns
repo.ignore_add?;
Repository::ignore_check(path) -> Result<bool>
Check if a file is ignored by .gitignore patterns.
// Check if file is ignored
let is_ignored = repo.ignore_check?;
if is_ignored
Repository::ignore_list() -> Result<Vec<String>>
List current ignore patterns from .gitignore.
// List all ignore patterns
let patterns = repo.ignore_list?;
for pattern in patterns
File Lifecycle Options
The file lifecycle operations use builder patterns for advanced configuration:
// RestoreOptions for advanced restore operations
let restore_options = new
.with_source // Restore from specific commit/branch
.with_staged // Restore staged files
.with_worktree; // Restore working tree files
// RemoveOptions for file removal
let remove_options = new
.with_force // Force removal
.with_recursive // Remove directories recursively
.with_cached // Remove from index only
.with_ignore_unmatch; // Don't fail if files don't match
// MoveOptions for file moves
let move_options = new
.with_force // Force move even if destination exists
.with_verbose // Show verbose output
.with_dry_run; // Dry run mode (don't actually move)
Commit Operations
Repository::commit(message) -> Result<Hash>
Create a commit with the given message.
let hash = repo.commit?;
println!;
println!;
Repository::commit_with_author(message, author) -> Result<Hash>
Create a commit with a custom author.
let hash = repo.commit_with_author?;
Branch Operations
Repository::branches() -> Result<BranchList>
List all branches in the repository.
let branches = repo.branches?;
// Check total count
println!;
println!;
println!;
// Iterate over all branches
for branch in branches.iter
// Filter by type
let local_branches: = branches.local.collect;
let remote_branches: = branches.remote.collect;
Repository::current_branch() -> Result<Option<Branch>>
Get the currently checked out branch.
if let Some = repo.current_branch?
Repository::create_branch(name, start_point) -> Result<Branch>
Create a new branch.
// Create branch from current HEAD
let branch = repo.create_branch?;
// Create branch from specific commit/branch
let branch = repo.create_branch?;
let branch = repo.create_branch?;
Repository::checkout(branch) -> Result<()>
Switch to an existing branch.
let branches = repo.branches?;
if let Some = branches.find
Repository::checkout_new(name, start_point) -> Result<Branch>
Create a new branch and switch to it immediately.
// Create and checkout new branch from current HEAD
let branch = repo.checkout_new?;
// Create and checkout from specific starting point
let branch = repo.checkout_new?;
println!;
Repository::delete_branch(branch, force) -> Result<()>
Delete a branch.
let branches = repo.branches?;
if let Some = branches.find
Branch Types
The branch API uses structured types for type safety:
// Branch represents a single branch
// Branch type enumeration
// BranchList contains all branches with efficient methods
Branch Search and Filtering
let branches = repo.branches?;
// Find specific branches
if let Some = branches.find
// Find by short name (useful for remote branches)
if let Some = branches.find_by_short_name
// Filter by type
println!;
for branch in branches.local
if branches.remote_count > 0
// Get current branch
if let Some = branches.current
Commit History Operations
Repository::log() -> Result<CommitLog>
Get all commits in the repository.
let commits = repo.log?;
println!;
for commit in commits.iter
Repository::recent_commits(count) -> Result<CommitLog>
Get the most recent N commits.
let recent = repo.recent_commits?;
for commit in recent.iter
Repository::log_with_options(options) -> Result<CommitLog>
Advanced commit queries with filtering options.
use ;
// Search commits with message containing "fix"
let bug_fixes = repo.log_with_options?;
// Get commits by specific author
let author_commits = repo.log_with_options?;
// Get commits from date range
let since = now - days;
let recent_commits = repo.log_with_options?;
// Get commits affecting specific paths
let file_commits = repo.log_with_options?;
Repository::log_range(from, to) -> Result<CommitLog>
Get commits between two specific commits.
// Get all commits between two hashes
let range_commits = repo.log_range?;
println!;
Repository::log_for_paths(paths) -> Result<CommitLog>
Get commits that affected specific files or directories.
// Get commits that modified specific files
let file_commits = repo.log_for_paths?;
// Get commits that affected a directory
let dir_commits = repo.log_for_paths?;
Repository::show_commit(hash) -> Result<CommitDetails>
Get detailed information about a specific commit including file changes.
let details = repo.show_commit?;
println!;
println!;
println!;
println!;
if let Some = &details.commit.message.body
println!;
for file in &details.files_changed
println!;
Commit Types and Filtering
The commit API provides rich types for working with commit data:
// Commit represents a single commit
// Author information with timestamp
// Parsed commit message
// Detailed commit information
CommitLog Filtering
CommitLog
provides iterator-based filtering methods:
let commits = repo.log?;
// Filter by message content
let bug_fixes: = commits.with_message_containing.collect;
let features: = commits.with_message_containing.collect;
// Filter by date
use ;
let last_week = now - weeks;
let recent: = commits.since.collect;
// Filter by commit type
let merge_commits: = commits.merges_only.collect;
let regular_commits: = commits.no_merges.collect;
// Search by hash
if let Some = commits.find_by_hash
if let Some = commits.find_by_short_hash
LogOptions Builder
LogOptions
provides a builder pattern for advanced queries:
let options = new
.max_count // Limit number of commits
.since // Since date
.until // Until date
.author // Filter by author
.committer // Filter by committer
.grep // Search in commit messages
.follow_renames // Follow file renames
.merges_only // Only merge commits
.no_merges // Exclude merge commits
.paths; // Filter by paths
let filtered_commits = repo.log_with_options?;
Hash Type
The Hash
type represents Git object hashes (commits, trees, blobs, etc.).
let hash = repo.commit?;
// Get full hash as string
let full_hash: &str = hash.as_str;
// Get short hash (first 7 characters)
let short_hash: &str = hash.short;
// Display formatting
println!; // Displays full hash
Error Handling
All operations return Result<T, GitError>
for proper error handling.
use ;
match repo.commit
Complete Workflow Example
use ;
use fs;
Diff Operations
The diff operations provide a comprehensive API for comparing different states in your Git repository. All diff operations return a DiffOutput
containing file changes and statistics.
Repository::diff() -> Result<DiffOutput>
Get differences between working directory and index (unstaged changes).
let diff = repo.diff?;
if diff.is_empty else
Repository::diff_staged() -> Result<DiffOutput>
Get differences between index and HEAD (staged changes).
let staged_diff = repo.diff_staged?;
println!;
// Filter by change type
let added_files: = staged_diff.files_with_status.collect;
let modified_files: = staged_diff.files_with_status.collect;
let deleted_files: = staged_diff.files_with_status.collect;
println!;
Repository::diff_head() -> Result<DiffOutput>
Get all differences between working directory and HEAD (both staged and unstaged).
let head_diff = repo.diff_head?;
println!;
for file in head_diff.iter
Repository::diff_commits(from, to) -> Result<DiffOutput>
Compare two specific commits.
let commits = repo.recent_commits?;
if commits.len >= 2
Repository::diff_with_options(options) -> Result<DiffOutput>
Advanced diff operations with custom options.
// Diff with custom options
let options = new
.ignore_whitespace // Ignore whitespace changes
.ignore_whitespace_change // Ignore whitespace amount changes
.ignore_blank_lines // Ignore blank line changes
.context_lines // Show 10 lines of context
.paths; // Only diff src/ directory
let diff = repo.diff_with_options?;
// Different output formats
let name_only = repo.diff_with_options?;
println!;
for file in name_only.iter
let stat_diff = repo.diff_with_options?;
println!;
let numstat_diff = repo.diff_with_options?;
for file in numstat_diff.iter
Diff Types and Data Structures
// Main diff output containing files and statistics
// Individual file changes
// Change status for files
// Aggregate statistics
Diff Options Builder
// Build custom diff options
let options = new
.context_lines // Lines of context around changes
.ignore_whitespace // --ignore-all-space
.ignore_whitespace_change // --ignore-space-change
.ignore_blank_lines // --ignore-blank-lines
.name_only // Show only file names
.stat_only // Show only statistics
.numstat // Show numerical statistics
.cached // Compare index with HEAD
.no_index // Compare files outside git
.paths; // Limit to specific paths
let diff = repo.diff_with_options?;
Working with Diff Results
let diff = repo.diff?;
// Check if any changes exist
if diff.is_empty
// Iterate over all changed files
for file in diff.iter
// Filter by specific change types
let new_files: = diff.files_with_status.collect;
let modified_files: = diff.files_with_status.collect;
let deleted_files: = diff.files_with_status.collect;
println!;
// Access aggregate statistics
println!;
println!;
Stash Operations
The stash operations provide a comprehensive API for temporarily saving and managing work-in-progress changes. All stash operations return structured data with type-safe filtering capabilities.
Repository::stash_save(message) -> Result<Stash>
Save current changes to a new stash with a message.
// Save current changes
let stash = repo.stash_save?;
println!;
println!;
Repository::stash_push(message, options) -> Result<Stash>
Create a stash with advanced options.
// Stash including untracked files
let options = new
.with_untracked // Include untracked files
.with_keep_index; // Keep staged changes in index
let stash = repo.stash_push?;
// Stash specific paths only
let path_options = new
.with_paths;
let partial_stash = repo.stash_push?;
Repository::stash_list() -> Result<StashList>
List all stashes with filtering capabilities.
let stashes = repo.stash_list?;
println!;
// Iterate over all stashes
for stash in stashes.iter
// Filter by message content
let wip_stashes: = stashes.find_containing.collect;
let feature_stashes: = stashes.find_containing.collect;
println!;
// Get specific stashes
if let Some = stashes.latest
if let Some = stashes.get
// Filter by branch
let main_stashes: = stashes.for_branch.collect;
println!;
Repository::stash_apply(index, options) -> Result<()>
Apply a stash without removing it from the stash list.
// Apply latest stash
repo.stash_apply?;
// Apply with index restoration (restore staging state)
let apply_options = new
.with_index // Restore staged state
.with_quiet; // Suppress output
repo.stash_apply?;
Repository::stash_pop(index, options) -> Result<()>
Apply a stash and remove it from the stash list.
// Pop latest stash
repo.stash_pop?;
// Pop with options
let pop_options = new.with_index;
repo.stash_pop?;
Repository::stash_show(index) -> Result<String>
Show the contents of a stash.
// Show latest stash contents
let stash_contents = repo.stash_show?;
println!;
// Show specific stash
let stash_contents = repo.stash_show?;
println!;
Repository::stash_drop(index) -> Result<()>
Remove a specific stash from the stash list.
// Drop a specific stash
repo.stash_drop?; // Remove second stash
println!;
Repository::stash_clear() -> Result<()>
Remove all stashes from the repository.
// Clear all stashes
repo.stash_clear?;
println!;
Stash Types and Data Structures
// Individual stash representation
// Collection of stashes with filtering methods
// Options for creating stashes
// Options for applying stashes
Stash Options Builder
// Build custom stash options
let stash_options = new
.with_untracked // Include untracked files (-u)
.with_keep_index // Keep staged changes (--keep-index)
.with_patch // Interactive mode (-p)
.with_staged_only // Only staged changes (--staged)
.with_paths;
let stash = repo.stash_push?;
// Build apply options
let apply_options = new
.with_index // Restore index state (--index)
.with_quiet; // Quiet mode (-q)
repo.stash_apply?;
Working with Stash Results
let stashes = repo.stash_list?;
// Check if any stashes exist
if stashes.is_empty
// Work with latest stash
if let Some = stashes.latest
// Filter and process stashes
let work_stashes: = stashes
.find_containing
.filter
.collect;
for stash in work_stashes
// Manage stash stack
println!;
for stash in stashes.iter.take
// Clean up old stashes (example: keep only recent 10)
if stashes.len > 10
Examples
The examples/
directory contains comprehensive demonstrations of library functionality:
Running Examples
# Complete workflow from init to commit
# Repository lifecycle operations
# Enhanced status API with staged/unstaged tracking
# Staging operations (add, add_all, add_update)
# Commit workflows and Hash type usage
# Branch operations (create, delete, checkout, list)
# Repository configuration management
# Commit history and log operations with advanced querying
# Remote management and network operations
# File lifecycle operations (restore, remove, move, .gitignore)
# Diff operations with multi-level API and comprehensive options
# Tag operations (create, list, delete, filter)
# Stash operations (save, apply, pop, list, manage)
# Error handling patterns and recovery strategies
Example Files
basic_usage.rs
- Demonstrates the fundamental rustic-git workflow: initialize a repository, create files, check status, stage changes, and create commitsrepository_operations.rs
- Shows repository lifecycle operations including initializing regular and bare repositories, opening existing repos, and handling errorsstatus_checking.rs
- Comprehensive demonstration of GitStatus and FileStatus usage with all query methods and filtering capabilitiesstaging_operations.rs
- Shows all staging methods (add, add_all, add_update) with before/after status comparisonscommit_workflows.rs
- Demonstrates commit operations and Hash type methods, including custom authors and hash managementbranch_operations.rs
- Complete branch management demonstration: create, checkout, delete branches, and BranchList filteringconfig_operations.rs
- Repository configuration management demonstration: user setup, configuration values, and repository-scoped settingscommit_history.rs
- Comprehensive commit history & log operations showing all querying APIs, filtering, analysis, and advanced LogOptions usageremote_operations.rs
- Complete remote management demonstration: add, remove, rename remotes, fetch/push operations with options, and network operationsdiff_operations.rs
- Comprehensive diff operations showcase: unstaged/staged diffs, commit comparisons, advanced options, filtering, and output formatsfile_lifecycle_operations.rs
- Comprehensive file management demonstration: restore, reset, remove, move operations, .gitignore management, and advanced file lifecycle workflowstag_operations.rs
- Complete tag management demonstration: create, list, delete, filter tags, lightweight vs annotated tags, tag options, and comprehensive tag workflowsstash_operations.rs
- Complete stash management demonstration: save, apply, pop, list stashes, advanced options (untracked files, keep index, specific paths), filtering, and comprehensive stash workflowserror_handling.rs
- Comprehensive error handling patterns showing GitError variants, recovery strategies, and best practices
All examples use OS-appropriate temporary directories and include automatic cleanup for safe execution.
Testing
Run the test suite:
All tests create temporary repositories in OS-appropriate temporary directories and clean up after themselves.
Contributing
We welcome contributions! Please follow these guidelines when contributing to rustic-git:
Code Standards
- Rust Edition: Use Rust edition 2024
- Style Guide: Follow the Rust style guide for naming conventions and formatting
- Code Quality: Implement best practices for code organization and maintainability
- No Emojis: Do not use emoji in code or commit messages
Design Principles
- Repository-centric API: Static lifecycle methods (
init
,open
) returnRepository
instances, instance methods for git operations - Module-based organization: Separate files for repository.rs, error.rs, with lib.rs for re-exports only
- Co-located unit tests: Tests within each module (
#[cfg(test)] mod tests
) rather than separate test files - Early validation: Always call
Repository::ensure_git()
before git operations to validate git availability - Path handling: Use
PathBuf
for internal storage,&Path
for method parameters and returns,impl AsRef<Path>
for flexibility - Error handling: Custom
GitError
enum withFrom<io::Error>
trait for ergonomic error propagation - Command execution: Use
std::process::Command
with proper error handling and stderr capture
Development Workflow
Before submitting a pull request, ensure your code passes all checks:
# Format code
# Build project
# Run all tests
# Run linting (no warnings allowed)
# Verify all examples work
Pull Request Guidelines
- Ensure all tests pass and examples run successfully
- Follow conventional commit format:
type(scope): description
- Use types like
feat
,fix
,docs
,style
,refactor
,test
,chore
- Keep commit messages concise and in present tense
- Make sure your changes align with the project's design principles
Roadmap
Future planned features:
- Tag operations (create, list, delete, push tags)
- Stash operations (save, apply, pop, list, manage)
- Merge and rebase operations
- Repository analysis (blame, statistics, health check)
Status
rustic-git provides a complete git workflow including repository management, status checking, staging operations, commits, branch operations, commit history analysis, remote management, network operations, comprehensive file lifecycle management, tag operations, and stash management.