use rustic_git::{DiffOptions, DiffStatus, Repository};
use std::{env, fs};
fn main() -> rustic_git::Result<()> {
println!("Rustic Git - Diff Operations Example\n");
let repo_path = env::temp_dir().join("rustic_git_diff_example");
if repo_path.exists() {
fs::remove_dir_all(&repo_path).ok();
}
println!("Working in temporary directory: {}", repo_path.display());
let repo = Repository::init(&repo_path, false)?;
println!("Repository initialized successfully\n");
let config = repo.config();
config.set_user("Test User", "test@example.com")?;
println!("=== Creating Initial Files ===");
let readme_path = repo_path.join("README.md");
let src_dir = repo_path.join("src");
fs::create_dir_all(&src_dir).unwrap();
let main_path = src_dir.join("main.rs");
let lib_path = src_dir.join("lib.rs");
fs::write(
&readme_path,
"# Test Project\n\nA sample project for testing diff operations.\n",
)
.unwrap();
fs::write(
&main_path,
"fn main() {\n println!(\"Hello, world!\");\n}\n",
)
.unwrap();
fs::write(
&lib_path,
"pub fn add(a: i32, b: i32) -> i32 {\n a + b\n}\n",
)
.unwrap();
println!("Created initial files: README.md, src/main.rs, src/lib.rs");
repo.add_all()?;
let initial_commit = repo.commit("feat: initial commit with basic files")?;
println!("Initial commit: {}\n", initial_commit.short());
println!("=== Testing Different Diff Operations ===");
println!("1. Diff with no changes:");
let diff = repo.diff()?;
if diff.is_empty() {
println!(" ✓ No changes detected (as expected)");
} else {
println!(" ✗ Unexpected changes found");
}
println!();
println!("2. Creating unstaged changes:");
fs::write(&readme_path, "# Test Project\n\nA sample project for testing diff operations.\n\n## Features\n- Git operations\n- Diff functionality\n").unwrap();
fs::write(&main_path, "fn main() {\n println!(\"Hello, world!\");\n println!(\"Testing diff operations!\");\n}\n").unwrap();
let diff = repo.diff()?;
println!(" Unstaged changes found:");
println!(" Files changed: {}", diff.len());
for file in diff.iter() {
println!(" - {} ({})", file.path.display(), file.status);
}
println!(" {}", diff.stats);
println!();
println!("3. Staging README.md and checking staged diff:");
repo.add(&[&readme_path])?;
let staged_diff = repo.diff_staged()?;
println!(" Staged changes:");
for file in staged_diff.iter() {
println!(" - {} ({})", file.path.display(), file.status);
}
println!(" {}", staged_diff.stats);
let unstaged_diff = repo.diff()?;
println!(" Remaining unstaged changes:");
for file in unstaged_diff.iter() {
println!(" - {} ({})", file.path.display(), file.status);
}
println!(" {}", unstaged_diff.stats);
println!();
println!("4. Using diff options (name-only):");
let name_only_diff = repo.diff_with_options(&DiffOptions::new().name_only())?;
println!(" Modified files (name-only):");
for file in name_only_diff.iter() {
println!(" - {}", file.path.display());
}
println!();
println!("5. Diff with path filtering (src/ only):");
let src_paths = vec![src_dir.clone()];
let filtered_diff = repo.diff_with_options(&DiffOptions::new().paths(src_paths))?;
println!(" Changes in src/ directory:");
for file in filtered_diff.iter() {
println!(" - {} ({})", file.path.display(), file.status);
}
println!();
repo.add_all()?;
let second_commit = repo.commit("feat: add features section and improve main function")?;
println!("Second commit: {}", second_commit.short());
println!("\n6. Diff between commits:");
let commit_diff = repo.diff_commits(&initial_commit, &second_commit)?;
println!(
" Changes from {} to {}:",
initial_commit.short(),
second_commit.short()
);
for file in commit_diff.iter() {
println!(
" - {} ({}) +{} -{}",
file.path.display(),
file.status,
file.additions,
file.deletions
);
}
println!(" {}", commit_diff.stats);
println!();
println!("7. Adding new file and checking diff:");
let test_path = repo_path.join("test.txt");
fs::write(
&test_path,
"This is a new test file.\nWith multiple lines.\n",
)
.unwrap();
let new_file_diff = repo.diff()?;
println!(" New file detected:");
for file in new_file_diff.iter() {
println!(" - {} ({})", file.path.display(), file.status);
}
println!();
println!("8. Deleting file and checking diff:");
fs::remove_file(&lib_path).unwrap();
let deleted_file_diff = repo.diff()?;
println!(" Changes after file deletion:");
for file in deleted_file_diff.iter() {
println!(" - {} ({})", file.path.display(), file.status);
}
println!();
println!("9. Testing whitespace options:");
fs::write(&main_path, "fn main() {\n println!(\"Hello, world!\");\n println!(\"Testing diff operations!\"); \n}\n").unwrap();
let normal_diff = repo.diff()?;
let whitespace_diff = repo.diff_with_options(&DiffOptions::new().ignore_whitespace())?;
println!(" Normal diff shows {} files changed", normal_diff.len());
println!(
" Whitespace-ignoring diff shows {} files changed",
whitespace_diff.len()
);
println!();
println!("10. Diff with HEAD (all changes since last commit):");
let head_diff = repo.diff_head()?;
println!(" All changes since last commit:");
for file in head_diff.iter() {
println!(" - {} ({})", file.path.display(), file.status);
}
println!(" {}", head_diff.stats);
println!();
println!("11. Testing different output formats:");
let stat_diff = repo.diff_with_options(&DiffOptions::new().stat_only())?;
println!(" Stat format:");
println!(" {}", stat_diff);
let numstat_diff = repo.diff_with_options(&DiffOptions::new().numstat())?;
println!(" Numstat format - {} files changed", numstat_diff.len());
for file in numstat_diff.iter() {
println!(
" {} +{} -{}",
file.path.display(),
file.additions,
file.deletions
);
}
println!();
println!("12. Filtering files by status:");
let all_changes = repo.diff_head()?;
let added_files: Vec<_> = all_changes.files_with_status(DiffStatus::Added).collect();
let modified_files: Vec<_> = all_changes
.files_with_status(DiffStatus::Modified)
.collect();
let deleted_files: Vec<_> = all_changes.files_with_status(DiffStatus::Deleted).collect();
println!(" Added files: {}", added_files.len());
for file in added_files {
println!(" - {}", file.path.display());
}
println!(" Modified files: {}", modified_files.len());
for file in modified_files {
println!(" - {}", file.path.display());
}
println!(" Deleted files: {}", deleted_files.len());
for file in deleted_files {
println!(" - {}", file.path.display());
}
println!();
println!("=== Diff Operations Demo Complete ===");
println!("All diff operations completed successfully!");
println!("Summary of tested features:");
println!("✓ Basic diff operations (working dir vs index)");
println!("✓ Staged diff operations (index vs HEAD)");
println!("✓ Diff between specific commits");
println!("✓ Diff with various options (name-only, stat, numstat)");
println!("✓ Path filtering");
println!("✓ Whitespace handling options");
println!("✓ File status filtering");
println!("✓ Comprehensive diff statistics");
println!("\nCleaning up temporary repository...");
fs::remove_dir_all(&repo_path).ok();
Ok(())
}