use rustic_git::{GitError, Repository, Result};
use std::{env, fs};
fn main() -> Result<()> {
println!("Rustic Git - Error Handling Example\n");
let base_path = env::temp_dir().join("rustic_git_error_example");
let repo_path = base_path.join("test_repo");
if base_path.exists() {
fs::remove_dir_all(&base_path).expect("Failed to clean up previous example");
}
fs::create_dir_all(&base_path).expect("Failed to create base directory");
println!("=== GitError Types and Handling ===\n");
demonstrate_repository_errors(&repo_path)?;
demonstrate_file_operation_errors(&repo_path)?;
demonstrate_git_command_errors(&repo_path)?;
demonstrate_error_recovery_patterns(&repo_path)?;
demonstrate_error_propagation_strategies(&base_path)?;
println!("Cleaning up error handling examples...");
fs::remove_dir_all(&base_path)?;
println!("Error handling example completed successfully!");
Ok(())
}
fn demonstrate_repository_errors(repo_path: &std::path::Path) -> Result<()> {
println!("Repository Error Scenarios:\n");
println!("1. Attempting to open non-existent repository:");
match Repository::open("/definitely/does/not/exist") {
Ok(_) => println!(" Unexpectedly succeeded"),
Err(GitError::IoError(msg)) => {
println!(" IoError caught: {}", msg);
println!(" This typically happens when the path doesn't exist");
}
Err(GitError::CommandFailed(msg)) => {
println!(" CommandFailed caught: {}", msg);
println!(" Git command failed - path exists but isn't a repo");
}
}
let fake_repo_path = repo_path.with_extension("fake.txt");
fs::write(&fake_repo_path, "This is not a git repository")?;
println!("\n2. Attempting to open regular file as repository:");
match Repository::open(&fake_repo_path) {
Ok(_) => println!(" Unexpectedly succeeded"),
Err(GitError::CommandFailed(msg)) => {
println!(" CommandFailed caught: {}", msg);
println!(" Git recognized the path but it's not a repository");
}
Err(GitError::IoError(msg)) => {
println!(" IoError caught: {}", msg);
}
}
fs::remove_file(&fake_repo_path)?;
println!("\n3. Attempting to initialize repository with problematic path:");
match Repository::init("/root/definitely_no_permission", false) {
Ok(_) => println!(" Unexpectedly succeeded (you might be running as root!)"),
Err(GitError::IoError(msg)) => {
println!(" IoError caught: {}", msg);
println!(" Likely a permission issue");
}
Err(GitError::CommandFailed(msg)) => {
println!(" CommandFailed caught: {}", msg);
println!(" Git init command failed");
}
}
println!();
Ok(())
}
fn demonstrate_file_operation_errors(repo_path: &std::path::Path) -> Result<()> {
println!("File Operation Error Scenarios:\n");
let repo = Repository::init(repo_path, false)?;
fs::write(repo_path.join("test.txt"), "Test content")?;
repo.add(&["test.txt"])?;
repo.commit("Initial commit")?;
println!("1. Attempting to add non-existent files:");
match repo.add(&["does_not_exist.txt", "also_missing.txt"]) {
Ok(_) => println!(" Unexpectedly succeeded"),
Err(GitError::CommandFailed(msg)) => {
println!(" CommandFailed caught: {}", msg);
println!(" Git add failed because files don't exist");
}
Err(GitError::IoError(msg)) => {
println!(" IoError caught: {}", msg);
}
}
println!("\n2. Adding mix of valid and invalid files:");
fs::write(repo_path.join("valid.txt"), "Valid file")?;
match repo.add(&["valid.txt", "invalid.txt"]) {
Ok(_) => {
println!(" Partially succeeded - some Git versions allow this");
let status = repo.status()?;
println!(" {} files staged despite error", status.entries.len());
}
Err(GitError::CommandFailed(msg)) => {
println!(" CommandFailed caught: {}", msg);
println!(" Entire add operation failed due to invalid file");
println!(" Recovery: Adding valid files individually...");
match repo.add(&["valid.txt"]) {
Ok(_) => println!(" Successfully added valid.txt"),
Err(e) => println!(" Recovery failed: {:?}", e),
}
}
Err(GitError::IoError(msg)) => {
println!(" IoError caught: {}", msg);
}
}
println!();
Ok(())
}
fn demonstrate_git_command_errors(repo_path: &std::path::Path) -> Result<()> {
println!("Git Command Error Scenarios:\n");
let repo = Repository::open(repo_path)?;
println!("1. Attempting commit with no staged changes:");
match repo.commit("Empty commit attempt") {
Ok(hash) => {
println!(" Unexpectedly succeeded: {}", hash.short());
println!(" Some Git configurations allow empty commits");
}
Err(GitError::CommandFailed(msg)) => {
println!(" CommandFailed caught: {}", msg);
println!(" Git requires changes to commit (normal behavior)");
}
Err(GitError::IoError(msg)) => {
println!(" IoError caught: {}", msg);
}
}
println!("\n2. Testing commit message edge cases:");
fs::write(
repo_path.join("commit_test.txt"),
"Content for commit testing",
)?;
repo.add(&["commit_test.txt"])?;
let very_long_message = "A ".repeat(1000) + "very long commit message";
match repo.commit(&very_long_message) {
Ok(hash) => {
println!(" Long commit message succeeded: {}", hash.short());
println!(" Git handled the long message fine");
}
Err(GitError::CommandFailed(msg)) => {
println!(" Long commit message failed: {}", msg);
}
Err(GitError::IoError(msg)) => {
println!(" IoError with long message: {}", msg);
}
}
println!();
Ok(())
}
fn demonstrate_error_recovery_patterns(repo_path: &std::path::Path) -> Result<()> {
println!("Error Recovery Patterns:\n");
let repo = Repository::open(repo_path)?;
println!("1. Retry Pattern - Graceful degradation:");
let files_to_add = ["missing1.txt", "missing2.txt", "missing3.txt"];
println!(" Attempting to add specific files...");
match repo.add(&files_to_add) {
Ok(_) => println!(" Specific files added successfully"),
Err(e) => {
println!(" Specific files failed: {:?}", e);
println!(" Falling back to add_all()...");
match repo.add_all() {
Ok(_) => {
let status = repo.status()?;
println!(
" add_all() succeeded, {} files staged",
status.entries.len()
);
}
Err(fallback_error) => {
println!(" Fallback also failed: {:?}", fallback_error);
}
}
}
}
println!("\n2. Partial Success Pattern:");
fs::write(repo_path.join("good1.txt"), "Good file 1")?;
fs::write(repo_path.join("good2.txt"), "Good file 2")?;
let mixed_files = ["good1.txt", "bad1.txt", "good2.txt"];
println!(" Attempting to add mixed valid/invalid files...");
match repo.add(&mixed_files) {
Ok(_) => println!(" All files added (unexpected success)"),
Err(GitError::CommandFailed(msg)) => {
println!(" Batch add failed: {}", msg);
println!(" Recovery: Adding files individually...");
let mut successful_adds = 0;
let mut failed_adds = 0;
for file in &mixed_files {
match repo.add(&[file]) {
Ok(_) => {
successful_adds += 1;
println!(" Added: {}", file);
}
Err(_) => {
failed_adds += 1;
println!(" Failed: {}", file);
}
}
}
println!(
" Results: {} succeeded, {} failed",
successful_adds, failed_adds
);
}
Err(GitError::IoError(msg)) => {
println!(" IoError during batch add: {}", msg);
}
}
println!("\n3. Preventive Pattern - Check before operation:");
println!(" Checking repository status before commit...");
let status = repo.status()?;
if status.is_clean() {
println!(" Repository is clean - no commit needed");
} else {
println!(" Repository has {} changes", status.entries.len());
for entry in &status.entries {
println!(
" Index {:?}, Worktree {:?}: {}",
entry.index_status,
entry.worktree_status,
entry.path.display()
);
}
match repo.commit("Commit after status check") {
Ok(hash) => println!(" Safe commit succeeded: {}", hash.short()),
Err(e) => println!(" Even safe commit failed: {:?}", e),
}
}
println!();
Ok(())
}
fn demonstrate_error_propagation_strategies(base_path: &std::path::Path) -> Result<()> {
println!("Error Propagation Strategies:\n");
println!("1. Early Return Strategy (using ?):");
match workflow_with_early_return(base_path) {
Ok(message) => println!(" Workflow completed: {}", message),
Err(e) => println!(" Workflow failed early: {:?}", e),
}
println!("\n2. Error Collection Strategy:");
let results = workflow_with_error_collection(base_path);
let successful = results.iter().filter(|r| r.is_ok()).count();
let failed = results.iter().filter(|r| r.is_err()).count();
println!(
" Operations: {} succeeded, {} failed",
successful, failed
);
for (i, result) in results.iter().enumerate() {
match result {
Ok(msg) => println!(" Step {}: {}", i + 1, msg),
Err(e) => println!(" Step {}: {:?}", i + 1, e),
}
}
println!("\n3. Error Context Strategy:");
match workflow_with_context(base_path) {
Ok(message) => println!(" Contextual workflow: {}", message),
Err(e) => println!(" Contextual workflow failed: {:?}", e),
}
println!();
Ok(())
}
fn workflow_with_early_return(base_path: &std::path::Path) -> Result<String> {
let repo_path = base_path.join("early_return_test");
let repo = Repository::init(&repo_path, false)?;
fs::write(repo_path.join("file1.txt"), "Content 1")?;
repo.add(&["file1.txt"])?;
let hash = repo.commit("Early return workflow commit")?;
fs::remove_dir_all(&repo_path)?;
Ok(format!("Completed with commit {}", hash.short()))
}
fn workflow_with_error_collection(base_path: &std::path::Path) -> Vec<Result<String>> {
let repo_path = base_path.join("error_collection_test");
let mut results = Vec::new();
results.push(Repository::init(&repo_path, false).map(|_| "Repository initialized".to_string()));
let files_to_create = ["good.txt", "another_good.txt"];
for file in &files_to_create {
results.push(
fs::write(repo_path.join(file), "Content")
.map_err(GitError::from)
.map(|_| format!("Created {}", file)),
);
}
if let Ok(repo) = Repository::open(&repo_path) {
results.push(
repo.add(&files_to_create)
.map(|_| "Files added to staging".to_string()),
);
results.push(
repo.commit("Error collection workflow")
.map(|hash| format!("Committed: {}", hash.short())),
);
} else {
results.push(Err(GitError::CommandFailed(
"Could not open repo for adding files".to_string(),
)));
results.push(Err(GitError::CommandFailed(
"Could not open repo for commit".to_string(),
)));
}
let _ = fs::remove_dir_all(&repo_path);
results
}
fn workflow_with_context(base_path: &std::path::Path) -> Result<String> {
let repo_path = base_path.join("context_test");
let repo = Repository::init(&repo_path, false).inspect_err(|_e| {
eprintln!(
"Context: Failed to initialize repository at {}",
repo_path.display()
);
})?;
fs::write(repo_path.join("context_file.txt"), "Content with context").map_err(|e| {
eprintln!("Context: Failed to create context_file.txt");
GitError::from(e)
})?;
repo.add(&["context_file.txt"]).inspect_err(|_e| {
eprintln!("Context: Failed to stage context_file.txt");
})?;
let hash = repo.commit("Context workflow commit").inspect_err(|_e| {
eprintln!("Context: Failed to create commit");
})?;
fs::remove_dir_all(&repo_path)?;
Ok(format!("Context workflow completed: {}", hash.short()))
}