diff --git a/src/main.rs b/src/main.rs index e0cbf70..013559e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use std::fs::File; use std::io::{Read, Write}; use std::{fs, path::PathBuf}; use std::path::Path; +use std::collections::HashMap; use sha256::{digest, try_digest}; fn main() -> std::io::Result<()> { @@ -120,6 +121,47 @@ fn main() -> std::io::Result<()> { std::io::copy(&mut decoder, &mut std::io::stdout())?; decoder.finish(); } + "diff" => { + if args.len() < 2 { + println!("diff: expected at least one argument (commit hash)"); + return Ok(()); + } + + let commit_hash_1 = &args[1]; + let commit_hash_2 = args.get(2); + + if let Some(commit_hash_2) = commit_hash_2 { + // Compare two commits + let commit1 = decompress_object(commit_hash_1)?; + let commit1 = Commit::from(commit1); + let commit2 = decompress_object(commit_hash_2)?; + let commit2 = Commit::from(commit2); + + let tree1 = Tree::from_hash(&commit1.tree); + let tree2 = Tree::from_hash(&commit2.tree); + + println!("Comparing commits:"); + println!(" {} ({})", commit_hash_1, commit1.message); + println!(" {} ({})", commit_hash_2, commit2.message); + println!(); + + diff_trees(&tree1, &tree2, Path::new("")); + } else { + // Compare commit with working directory + let commit = decompress_object(commit_hash_1)?; + let commit = Commit::from(commit); + let tree = Tree::from_hash(&commit.tree); + + println!("Comparing commit {} with working directory", commit_hash_1); + println!(); + + // Generate current working tree + let current_tree_str = generate_tree(Path::new("./"), &ignore_rules)?; + let current_tree = Tree::from_string(¤t_tree_str); + + diff_trees(&tree, ¤t_tree, Path::new("")); + } + } any => panic!("command \"{}\" not yet implemented", any) } } @@ -323,3 +365,89 @@ fn compress_file(source: &Path, dest: &Path) -> std::io::Result<()> { Ok(()) } +fn diff_trees(tree1: &Tree, tree2: &Tree, path: &Path) { + // Compare blobs (files) + let blobs1: HashMap = tree1.blobs.iter() + .map(|(name, hash)| (name.clone(), hash.clone())) + .collect(); + let blobs2: HashMap = tree2.blobs.iter() + .map(|(name, hash)| (name.clone(), hash.clone())) + .collect(); + + // Find added files + for (name, _hash) in &blobs2 { + if !blobs1.contains_key(name) { + let full_path = if path.as_os_str().is_empty() { + PathBuf::from(name) + } else { + path.join(name) + }; + println!("+ {}", full_path.display()); + } + } + + // Find removed and modified files + for (name, hash1) in &blobs1 { + let full_path = if path.as_os_str().is_empty() { + PathBuf::from(name) + } else { + path.join(name) + }; + + if let Some(hash2) = blobs2.get(name) { + if hash1 != hash2 { + println!("M {}", full_path.display()); + } + } else { + println!("- {}", full_path.display()); + } + } + + // Compare subtrees (directories) + let trees1: HashMap = tree1.trees.iter() + .map(|(name, tree)| (name.clone(), tree)) + .collect(); + let trees2: HashMap = tree2.trees.iter() + .map(|(name, tree)| (name.clone(), tree)) + .collect(); + + // Find added directories + for (name, tree) in &trees2 { + if !trees1.contains_key(name) { + let full_path = if path.as_os_str().is_empty() { + PathBuf::from(name) + } else { + path.join(name) + }; + // Show all files in the new directory + show_all_files(tree, &full_path, "+"); + } + } + + // Find removed and modified directories + for (name, tree1) in &trees1 { + let full_path = if path.as_os_str().is_empty() { + PathBuf::from(name) + } else { + path.join(name) + }; + + if let Some(tree2) = trees2.get(name) { + // Recursively compare + diff_trees(tree1, tree2, &full_path); + } else { + // Show all files in the removed directory + show_all_files(tree1, &full_path, "-"); + } + } +} + +fn show_all_files(tree: &Tree, path: &Path, prefix: &str) { + for (name, _) in &tree.blobs { + println!("{} {}", prefix, path.join(name).display()); + } + for (name, subtree) in &tree.trees { + show_all_files(subtree, &path.join(name), prefix); + } +} + diff --git a/todo.md b/todo.md index 9dd34c7..bd2175d 100644 --- a/todo.md +++ b/todo.md @@ -10,4 +10,4 @@ 8. [X] commit objects 9. [X] HEAD 10. [X] checkout -11. [ ] diff object +11. [X] diff object