Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
Expand Down Expand Up @@ -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(&current_tree_str);

diff_trees(&tree, &current_tree, Path::new(""));
}
}
any => panic!("command \"{}\" not yet implemented", any)
}
}
Expand Down Expand Up @@ -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<String, String> = tree1.blobs.iter()
.map(|(name, hash)| (name.clone(), hash.clone()))
.collect();
let blobs2: HashMap<String, String> = 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<String, &Tree> = tree1.trees.iter()
.map(|(name, tree)| (name.clone(), tree))
.collect();
let trees2: HashMap<String, &Tree> = 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);
}
}

2 changes: 1 addition & 1 deletion todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
8. [X] commit objects
9. [X] HEAD
10. [X] checkout
11. [ ] diff object
11. [X] diff object