From eb0bf60b9d69e18074e2e87e95225d67ce6d9201 Mon Sep 17 00:00:00 2001 From: zhangfengcdt Date: Tue, 15 Jul 2025 16:57:12 -0700 Subject: [PATCH 1/3] Enhance tree diffing and merging functions --- examples/git_diff_example.rs | 220 +++++++++++++++++++ examples/git_merge_example.rs | 401 ++++++++++++++++++++++++++++++++++ src/lib.rs | 2 +- 3 files changed, 622 insertions(+), 1 deletion(-) create mode 100644 examples/git_diff_example.rs create mode 100644 examples/git_merge_example.rs diff --git a/examples/git_diff_example.rs b/examples/git_diff_example.rs new file mode 100644 index 0000000..36feaed --- /dev/null +++ b/examples/git_diff_example.rs @@ -0,0 +1,220 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +//! Git-like Tree Diffing Example +//! +//! This example demonstrates how to use Prolly Trees to diff two trees that +//! have evolved separately from a common root, similar to git branch diffing. +//! It shows how changes can be detected and analyzed between different versions +//! of data structures. + +use prollytree::config::TreeConfig; +use prollytree::diff::DiffResult; +use prollytree::storage::InMemoryNodeStorage; +use prollytree::tree::{ProllyTree, Tree}; + +fn main() { + println!("🔀 Git-like Tree Diffing Example 🔀\n"); + + // Create a shared configuration for consistency + let config = TreeConfig { + base: 131, + modulus: 1_000_000_009, + min_chunk_size: 4, + max_chunk_size: 8 * 1024, + pattern: 0b101, + root_hash: None, + key_schema: None, + value_schema: None, + encode_types: vec![], + }; + + // ============================================================================ + // Step 1: Create a common "main" branch with initial data + // ============================================================================ + println!("📦 Creating main branch with initial data..."); + let storage_main = InMemoryNodeStorage::<32>::default(); + let mut main_tree = ProllyTree::new(storage_main, config.clone()); + + // Add initial commit data + let initial_data = vec![ + ("file1.txt", "Hello World"), + ("file2.txt", "Initial content"), + ("config.json", r#"{"version": "1.0", "debug": false}"#), + ("readme.md", "# Project\nThis is the main project"), + ("src/main.rs", "fn main() { println!(\"Hello\"); }"), + ]; + + for (key, value) in &initial_data { + main_tree.insert(key.as_bytes().to_vec(), value.as_bytes().to_vec()); + } + + println!(" ✅ Main branch created with {} files", initial_data.len()); + println!(" 🌳 Main tree hash: {:?}", main_tree.get_root_hash()); + + // ============================================================================ + // Step 2: Create "feature" branch - simulating git checkout -b feature + // ============================================================================ + println!("\n🌿 Creating feature branch from main..."); + let storage_feature = InMemoryNodeStorage::<32>::default(); + let mut feature_tree = ProllyTree::new(storage_feature, config.clone()); + + // Copy all data from main (simulating branch creation) + for (key, value) in &initial_data { + feature_tree.insert(key.as_bytes().to_vec(), value.as_bytes().to_vec()); + } + + // Make changes in feature branch + let feature_changes = vec![ + // Modified files + ("file1.txt", "Hello World - Feature Edition!"), + ("config.json", r#"{"version": "1.1", "debug": true, "feature_flag": true}"#), + + // New files + ("feature.rs", "pub fn new_feature() { /* implementation */ }"), + ("tests/test_feature.rs", "#[test] fn test_new_feature() { assert!(true); }"), + ]; + + for (key, value) in &feature_changes { + feature_tree.insert(key.as_bytes().to_vec(), value.as_bytes().to_vec()); + } + + // Delete a file in feature branch + feature_tree.delete(b"readme.md"); + + println!(" ✅ Feature branch created with modifications"); + println!(" 🌳 Feature tree hash: {:?}", feature_tree.get_root_hash()); + + // ============================================================================ + // Step 3: Create "hotfix" branch - another parallel development + // ============================================================================ + println!("\n🚀 Creating hotfix branch from main..."); + let storage_hotfix = InMemoryNodeStorage::<32>::default(); + let mut hotfix_tree = ProllyTree::new(storage_hotfix, config); + + // Copy all data from main + for (key, value) in &initial_data { + hotfix_tree.insert(key.as_bytes().to_vec(), value.as_bytes().to_vec()); + } + + // Make urgent fixes + let hotfix_changes = vec![ + ("file2.txt", "Fixed critical bug in initial content"), + ("src/main.rs", "fn main() { println!(\"Hello - Fixed!\"); }"), + ("hotfix.patch", "Critical security patch applied"), + ]; + + for (key, value) in &hotfix_changes { + hotfix_tree.insert(key.as_bytes().to_vec(), value.as_bytes().to_vec()); + } + + println!(" ✅ Hotfix branch created with urgent fixes"); + println!(" 🌳 Hotfix tree hash: {:?}", hotfix_tree.get_root_hash()); + + // ============================================================================ + // Step 4: Perform Git-like diffs between branches + // ============================================================================ + println!("\n🔍 Performing Git-like diffs...\n"); + + // Diff 1: main vs feature (like git diff main..feature) + println!("📊 Diff: main -> feature (shows what feature adds/changes)"); + println!(" Similar to: git diff main..feature"); + let main_to_feature_diff = main_tree.diff(&feature_tree); + print_diff_summary(&main_to_feature_diff, "main", "feature"); + + // Diff 2: main vs hotfix (like git diff main..hotfix) + println!("\n📊 Diff: main -> hotfix (shows what hotfix adds/changes)"); + println!(" Similar to: git diff main..hotfix"); + let main_to_hotfix_diff = main_tree.diff(&hotfix_tree); + print_diff_summary(&main_to_hotfix_diff, "main", "hotfix"); + + // Diff 3: feature vs hotfix (like git diff feature..hotfix) + println!("\n📊 Diff: feature -> hotfix (shows differences between parallel branches)"); + println!(" Similar to: git diff feature..hotfix"); + let feature_to_hotfix_diff = feature_tree.diff(&hotfix_tree); + print_diff_summary(&feature_to_hotfix_diff, "feature", "hotfix"); + + // ============================================================================ + // Step 5: Show detailed analysis + // ============================================================================ + println!("\n📈 Detailed Diff Analysis:"); + println!("═══════════════════════════════════════"); + + analyze_diff(&main_to_feature_diff, "Main → Feature"); + analyze_diff(&main_to_hotfix_diff, "Main → Hotfix"); + analyze_diff(&feature_to_hotfix_diff, "Feature ↔ Hotfix"); + + // ============================================================================ + // Summary + // ============================================================================ + println!("\n🎯 Summary:"); + println!(" • Prolly Trees enable efficient git-like diffing"); + println!(" • Each branch maintains its own merkle tree structure"); + println!(" • Diffs show exact changes: additions, deletions, modifications"); + println!(" • Perfect for version control, distributed systems, and data synchronization"); + println!(" • Hash-based verification ensures data integrity across branches"); +} + +fn print_diff_summary(diffs: &[DiffResult], _from_branch: &str, _to_branch: &str) { + let (added, removed, modified) = count_diff_types(diffs); + + println!(" 📈 Changes: +{} files, -{} files, ~{} files modified", added, removed, modified); + + if diffs.is_empty() { + println!(" ✨ No differences found - branches are identical"); + } +} + +fn count_diff_types(diffs: &[DiffResult]) -> (usize, usize, usize) { + let mut added = 0; + let mut removed = 0; + let mut modified = 0; + + for diff in diffs { + match diff { + DiffResult::Added(_, _) => added += 1, + DiffResult::Removed(_, _) => removed += 1, + DiffResult::Modified(_, _, _) => modified += 1, + } + } + + (added, removed, modified) +} + +fn analyze_diff(diffs: &[DiffResult], comparison: &str) { + println!("\n🔍 {}", comparison); + println!("───────────────────────────────"); + + if diffs.is_empty() { + println!(" No changes detected"); + return; + } + + for diff in diffs { + match diff { + DiffResult::Added(key, _value) => { + let filename = String::from_utf8_lossy(key); + println!(" ➕ Added: {}", filename); + } + DiffResult::Removed(key, _value) => { + let filename = String::from_utf8_lossy(key); + println!(" ➖ Removed: {}", filename); + } + DiffResult::Modified(key, _old_value, _new_value) => { + let filename = String::from_utf8_lossy(key); + println!(" 🔄 Modified: {}", filename); + } + } + } +} diff --git a/examples/git_merge_example.rs b/examples/git_merge_example.rs new file mode 100644 index 0000000..e5e1b48 --- /dev/null +++ b/examples/git_merge_example.rs @@ -0,0 +1,401 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +//! Git-like Tree Merging Example +//! +//! This example demonstrates how to merge two Prolly Trees that have evolved +//! separately from a common root, similar to git branch merging. It shows +//! how to handle different types of conflicts and create a unified tree. + +use prollytree::config::TreeConfig; +use prollytree::diff::DiffResult; +use prollytree::storage::InMemoryNodeStorage; +use prollytree::tree::{ProllyTree, Tree}; +use std::collections::HashMap; + +#[derive(Debug, Clone)] +enum MergeConflict { + BothModified { + key: Vec, + branch_a_value: Vec, + branch_b_value: Vec, + }, +} + + +fn main() { + println!("🔀 Git-like Tree Merging Example 🔀\n"); + + // Create a shared configuration for consistency + let config = TreeConfig { + base: 131, + modulus: 1_000_000_009, + min_chunk_size: 4, + max_chunk_size: 8 * 1024, + pattern: 0b101, + root_hash: None, + key_schema: None, + value_schema: None, + encode_types: vec![], + }; + + // ============================================================================ + // Scenario 1: Fast-forward merge (no conflicts) + // ============================================================================ + println!("🚀 Scenario 1: Fast-forward merge"); + println!("═══════════════════════════════════"); + + let (main_tree_ff, feature_tree_ff) = create_fast_forward_scenario(config.clone()); + + println!(" 📊 Before merge:"); + println!(" Main branch: {} files", main_tree_ff.size()); + println!(" Feature branch: {} files", feature_tree_ff.size()); + + let merged_ff = perform_fast_forward_merge(&main_tree_ff, &feature_tree_ff); + println!(" ✅ Fast-forward merge completed!"); + println!(" Merged tree: {} files", merged_ff.size()); + println!(" Strategy: Fast-forward (no conflicts)\n"); + + // ============================================================================ + // Scenario 2: Three-way merge with automatic resolution + // ============================================================================ + println!("🔄 Scenario 2: Three-way merge (auto-resolvable)"); + println!("═══════════════════════════════════════════════"); + + let (base_tree, branch_a, branch_b) = create_three_way_scenario(config.clone()); + + println!(" 📊 Before merge:"); + println!(" Base (common ancestor): {} files", base_tree.size()); + println!(" Branch A: {} files", branch_a.size()); + println!(" Branch B: {} files", branch_b.size()); + + let merged_3way = perform_three_way_merge(&base_tree, &branch_a, &branch_b); + println!(" ✅ Three-way merge completed!"); + println!(" Merged tree: {} files", merged_3way.size()); + println!(" Strategy: Three-way merge (auto-resolved)\n"); + + // ============================================================================ + // Scenario 3: Merge with conflicts requiring resolution + // ============================================================================ + println!("⚠️ Scenario 3: Merge with conflicts"); + println!("══════════════════════════════════════"); + + let (base_conflict, branch_conflict_a, branch_conflict_b) = create_conflict_scenario(config); + + println!(" 📊 Before merge:"); + println!(" Base: {} files", base_conflict.size()); + println!(" Branch A: {} files", branch_conflict_a.size()); + println!(" Branch B: {} files", branch_conflict_b.size()); + + let (conflicts, merged_with_conflicts) = detect_and_resolve_conflicts( + &base_conflict, + &branch_conflict_a, + &branch_conflict_b + ); + + if !conflicts.is_empty() { + println!(" 🚨 Conflicts detected: {} conflicts", conflicts.len()); + for conflict in &conflicts { + print_conflict(conflict); + } + println!(" ✅ Conflicts resolved automatically!"); + } + + println!(" Merged tree: {} files", merged_with_conflicts.size()); + println!(" Strategy: Conflict resolution\n"); + + // ============================================================================ + // Summary and Best Practices + // ============================================================================ + println!("🎯 Summary & Best Practices:"); + println!("═══════════════════════════════"); + println!(" 1. 🚀 Fast-forward: When target branch is ahead of source"); + println!(" 2. 🔄 Three-way: When both branches have changes from common base"); + println!(" 3. ⚠️ Conflicts: When same data is modified differently"); + println!(" 4. 🔍 Always verify merge results with diff analysis"); + println!(" 5. 💾 Prolly Trees provide cryptographic verification of merges"); + println!(" 6. 🌳 Each merge creates a new tree with verifiable history"); + + // Show the final verification + println!("\n🔍 Merge Verification:"); + println!(" Fast-forward hash: {:?}", merged_ff.get_root_hash()); + println!(" Three-way hash: {:?}", merged_3way.get_root_hash()); + println!(" Conflict-resolved hash: {:?}", merged_with_conflicts.get_root_hash()); +} + +fn create_fast_forward_scenario(config: TreeConfig<32>) -> (ProllyTree<32, InMemoryNodeStorage<32>>, ProllyTree<32, InMemoryNodeStorage<32>>) { + // Main branch (behind) + let storage_main = InMemoryNodeStorage::<32>::default(); + let mut main_tree = ProllyTree::new(storage_main, config.clone()); + + main_tree.insert(b"readme.md".to_vec(), b"# Project".to_vec()); + main_tree.insert(b"src/lib.rs".to_vec(), b"// Library code".to_vec()); + + // Feature branch (ahead with additional commits) + let storage_feature = InMemoryNodeStorage::<32>::default(); + let mut feature_tree = ProllyTree::new(storage_feature, config); + + // Start with main's content + feature_tree.insert(b"readme.md".to_vec(), b"# Project".to_vec()); + feature_tree.insert(b"src/lib.rs".to_vec(), b"// Library code".to_vec()); + + // Add new features + feature_tree.insert(b"src/feature.rs".to_vec(), b"pub fn new_feature() {}".to_vec()); + feature_tree.insert(b"tests/test.rs".to_vec(), b"#[test] fn test_feature() {}".to_vec()); + + (main_tree, feature_tree) +} + +fn create_three_way_scenario(config: TreeConfig<32>) -> ( + ProllyTree<32, InMemoryNodeStorage<32>>, + ProllyTree<32, InMemoryNodeStorage<32>>, + ProllyTree<32, InMemoryNodeStorage<32>> +) { + // Base (common ancestor) + let storage_base = InMemoryNodeStorage::<32>::default(); + let mut base_tree = ProllyTree::new(storage_base, config.clone()); + + base_tree.insert(b"config.json".to_vec(), b"{\"version\": \"1.0\"}".to_vec()); + base_tree.insert(b"main.rs".to_vec(), b"fn main() {}".to_vec()); + + // Branch A: adds documentation + let storage_a = InMemoryNodeStorage::<32>::default(); + let mut branch_a = ProllyTree::new(storage_a, config.clone()); + + branch_a.insert(b"config.json".to_vec(), b"{\"version\": \"1.0\"}".to_vec()); + branch_a.insert(b"main.rs".to_vec(), b"fn main() {}".to_vec()); + branch_a.insert(b"docs.md".to_vec(), b"# Documentation".to_vec()); + + // Branch B: adds tests + let storage_b = InMemoryNodeStorage::<32>::default(); + let mut branch_b = ProllyTree::new(storage_b, config); + + branch_b.insert(b"config.json".to_vec(), b"{\"version\": \"1.0\"}".to_vec()); + branch_b.insert(b"main.rs".to_vec(), b"fn main() {}".to_vec()); + branch_b.insert(b"test.rs".to_vec(), b"#[test] fn test() {}".to_vec()); + + (base_tree, branch_a, branch_b) +} + +fn create_conflict_scenario(config: TreeConfig<32>) -> ( + ProllyTree<32, InMemoryNodeStorage<32>>, + ProllyTree<32, InMemoryNodeStorage<32>>, + ProllyTree<32, InMemoryNodeStorage<32>> +) { + // Base + let storage_base = InMemoryNodeStorage::<32>::default(); + let mut base_tree = ProllyTree::new(storage_base, config.clone()); + + base_tree.insert(b"version.txt".to_vec(), b"1.0.0".to_vec()); + base_tree.insert(b"shared.rs".to_vec(), b"// Shared code".to_vec()); + + // Branch A: updates version and modifies shared code + let storage_a = InMemoryNodeStorage::<32>::default(); + let mut branch_a = ProllyTree::new(storage_a, config.clone()); + + branch_a.insert(b"version.txt".to_vec(), b"1.1.0".to_vec()); // Conflict! + branch_a.insert(b"shared.rs".to_vec(), b"// Shared code - Feature A".to_vec()); // Conflict! + branch_a.insert(b"feature_a.rs".to_vec(), b"// Feature A".to_vec()); + + // Branch B: updates version differently and modifies shared code differently + let storage_b = InMemoryNodeStorage::<32>::default(); + let mut branch_b = ProllyTree::new(storage_b, config); + + branch_b.insert(b"version.txt".to_vec(), b"1.0.1".to_vec()); // Conflict! + branch_b.insert(b"shared.rs".to_vec(), b"// Shared code - Feature B".to_vec()); // Conflict! + branch_b.insert(b"feature_b.rs".to_vec(), b"// Feature B".to_vec()); + + (base_tree, branch_a, branch_b) +} + +fn perform_fast_forward_merge( + _main: &ProllyTree<32, InMemoryNodeStorage<32>>, + feature: &ProllyTree<32, InMemoryNodeStorage<32>> +) -> ProllyTree<32, InMemoryNodeStorage<32>> { + // In a fast-forward merge, we simply adopt the feature branch + // since it contains all of main's changes plus additional ones + + let storage = InMemoryNodeStorage::<32>::default(); + let mut merged = ProllyTree::new(storage, TreeConfig::default()); + + // Copy all data from feature branch (which includes main + new changes) + collect_all_key_values(feature).into_iter().for_each(|(k, v)| { + merged.insert(k, v); + }); + + merged +} + +fn perform_three_way_merge( + base: &ProllyTree<32, InMemoryNodeStorage<32>>, + branch_a: &ProllyTree<32, InMemoryNodeStorage<32>>, + branch_b: &ProllyTree<32, InMemoryNodeStorage<32>> +) -> ProllyTree<32, InMemoryNodeStorage<32>> { + let storage = InMemoryNodeStorage::<32>::default(); + let mut merged = ProllyTree::new(storage, TreeConfig::default()); + + // Start with base + for (key, value) in collect_all_key_values(base) { + merged.insert(key, value); + } + + // Apply changes from branch A + let diff_a = base.diff(branch_a); + apply_diff_to_tree(&mut merged, &diff_a); + + // Apply non-conflicting changes from branch B + let diff_b = base.diff(branch_b); + apply_diff_to_tree(&mut merged, &diff_b); + + merged +} + +fn detect_and_resolve_conflicts( + base: &ProllyTree<32, InMemoryNodeStorage<32>>, + branch_a: &ProllyTree<32, InMemoryNodeStorage<32>>, + branch_b: &ProllyTree<32, InMemoryNodeStorage<32>> +) -> (Vec, ProllyTree<32, InMemoryNodeStorage<32>>) { + let mut conflicts = Vec::new(); + + let diff_a = base.diff(branch_a); + let diff_b = base.diff(branch_b); + + // Group diffs by key to detect conflicts + let mut changes_a: HashMap, &DiffResult> = HashMap::new(); + let mut changes_b: HashMap, &DiffResult> = HashMap::new(); + + for diff in &diff_a { + let key = get_diff_key(diff); + changes_a.insert(key, diff); + } + + for diff in &diff_b { + let key = get_diff_key(diff); + changes_b.insert(key, diff); + } + + // Detect conflicts + for (key, diff_a) in &changes_a { + if let Some(diff_b) = changes_b.get(key) { + // Both branches modified the same file + if let (DiffResult::Modified(_, _, new_a), DiffResult::Modified(_, _, new_b)) = (diff_a, diff_b) { + conflicts.push(MergeConflict::BothModified { + key: key.clone(), + branch_a_value: new_a.clone(), + branch_b_value: new_b.clone(), + }); + } + } + } + + // Create merged tree with conflict resolution + let storage = InMemoryNodeStorage::<32>::default(); + let mut merged = ProllyTree::new(storage, TreeConfig::default()); + + // Start with base + for (key, value) in collect_all_key_values(base) { + merged.insert(key, value); + } + + // Apply non-conflicting changes + for diff in &diff_a { + let key = get_diff_key(diff); + if !conflicts.iter().any(|c| get_conflict_key(c) == &key) { + apply_single_diff(&mut merged, diff); + } + } + + for diff in &diff_b { + let key = get_diff_key(diff); + if !conflicts.iter().any(|c| get_conflict_key(c) == &key) { + apply_single_diff(&mut merged, diff); + } + } + + // Resolve conflicts (using a simple strategy: prefer branch A) + for conflict in &conflicts { + match conflict { + MergeConflict::BothModified { key, branch_a_value, .. } => { + merged.insert(key.clone(), branch_a_value.clone()); + } + } + } + + (conflicts, merged) +} + +fn collect_all_key_values(tree: &ProllyTree<32, InMemoryNodeStorage<32>>) -> Vec<(Vec, Vec)> { + // This is a simplified implementation + // In practice, you'd traverse the tree to collect all key-value pairs + let mut result = Vec::new(); + + // For this example, we'll use the diff against an empty tree to get all entries + let empty_storage = InMemoryNodeStorage::<32>::default(); + let empty_tree = ProllyTree::new(empty_storage, TreeConfig::default()); + + let diff = empty_tree.diff(tree); + for diff_result in diff { + if let DiffResult::Added(key, value) = diff_result { + result.push((key, value)); + } + } + + result +} + +fn apply_diff_to_tree(tree: &mut ProllyTree<32, InMemoryNodeStorage<32>>, diffs: &[DiffResult]) { + for diff in diffs { + apply_single_diff(tree, diff); + } +} + +fn apply_single_diff(tree: &mut ProllyTree<32, InMemoryNodeStorage<32>>, diff: &DiffResult) { + match diff { + DiffResult::Added(key, value) => { + tree.insert(key.clone(), value.clone()); + } + DiffResult::Modified(key, _, new_value) => { + tree.insert(key.clone(), new_value.clone()); + } + DiffResult::Removed(key, _) => { + tree.delete(key); + } + } +} + +fn get_diff_key(diff: &DiffResult) -> Vec { + match diff { + DiffResult::Added(key, _) => key.clone(), + DiffResult::Removed(key, _) => key.clone(), + DiffResult::Modified(key, _, _) => key.clone(), + } +} + +fn get_conflict_key(conflict: &MergeConflict) -> &Vec { + match conflict { + MergeConflict::BothModified { key, .. } => key, + } +} + +fn print_conflict(conflict: &MergeConflict) { + match conflict { + MergeConflict::BothModified { key, branch_a_value, branch_b_value, .. } => { + let filename = String::from_utf8_lossy(key); + println!(" ⚠️ Both modified: {}", filename); + println!(" Branch A: {}", String::from_utf8_lossy(branch_a_value)); + println!(" Branch B: {}", String::from_utf8_lossy(branch_b_value)); + println!(" Resolution: Using Branch A version"); + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index a3427c7..25cc193 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,7 @@ limitations under the License. #[macro_use] pub mod digest; pub mod config; -mod diff; +pub mod diff; mod encoding; pub mod errors; pub mod node; From 06bd6b3beb1efbb20b586cf494bdeb6a7f2bf5a5 Mon Sep 17 00:00:00 2001 From: zhangfengcdt Date: Tue, 15 Jul 2025 17:08:42 -0700 Subject: [PATCH 2/3] use colors instead of icons --- examples/git_diff_example.rs | 98 +++++++----- examples/git_merge_example.rs | 284 ++++++++++++++++++++-------------- 2 files changed, 227 insertions(+), 155 deletions(-) diff --git a/examples/git_diff_example.rs b/examples/git_diff_example.rs index 36feaed..bcc882b 100644 --- a/examples/git_diff_example.rs +++ b/examples/git_diff_example.rs @@ -25,7 +25,7 @@ use prollytree::storage::InMemoryNodeStorage; use prollytree::tree::{ProllyTree, Tree}; fn main() { - println!("🔀 Git-like Tree Diffing Example 🔀\n"); + println!("\x1b[1;36mGit-like Tree Diffing Example\x1b[0m\n"); // Create a shared configuration for consistency let config = TreeConfig { @@ -43,7 +43,7 @@ fn main() { // ============================================================================ // Step 1: Create a common "main" branch with initial data // ============================================================================ - println!("📦 Creating main branch with initial data..."); + println!("\x1b[1;32mCreating main branch with initial data...\x1b[0m"); let storage_main = InMemoryNodeStorage::<32>::default(); let mut main_tree = ProllyTree::new(storage_main, config.clone()); @@ -60,13 +60,19 @@ fn main() { main_tree.insert(key.as_bytes().to_vec(), value.as_bytes().to_vec()); } - println!(" ✅ Main branch created with {} files", initial_data.len()); - println!(" 🌳 Main tree hash: {:?}", main_tree.get_root_hash()); + println!( + " \x1b[32mMain branch created with {} files\x1b[0m", + initial_data.len() + ); + println!( + " \x1b[33mMain tree hash: {:?}\x1b[0m", + main_tree.get_root_hash() + ); // ============================================================================ // Step 2: Create "feature" branch - simulating git checkout -b feature // ============================================================================ - println!("\n🌿 Creating feature branch from main..."); + println!("\n\x1b[1;32mCreating feature branch from main...\x1b[0m"); let storage_feature = InMemoryNodeStorage::<32>::default(); let mut feature_tree = ProllyTree::new(storage_feature, config.clone()); @@ -79,11 +85,19 @@ fn main() { let feature_changes = vec![ // Modified files ("file1.txt", "Hello World - Feature Edition!"), - ("config.json", r#"{"version": "1.1", "debug": true, "feature_flag": true}"#), - + ( + "config.json", + r#"{"version": "1.1", "debug": true, "feature_flag": true}"#, + ), // New files - ("feature.rs", "pub fn new_feature() { /* implementation */ }"), - ("tests/test_feature.rs", "#[test] fn test_new_feature() { assert!(true); }"), + ( + "feature.rs", + "pub fn new_feature() { /* implementation */ }", + ), + ( + "tests/test_feature.rs", + "#[test] fn test_new_feature() { assert!(true); }", + ), ]; for (key, value) in &feature_changes { @@ -93,13 +107,16 @@ fn main() { // Delete a file in feature branch feature_tree.delete(b"readme.md"); - println!(" ✅ Feature branch created with modifications"); - println!(" 🌳 Feature tree hash: {:?}", feature_tree.get_root_hash()); + println!(" \x1b[32mFeature branch created with modifications\x1b[0m"); + println!( + " \x1b[33mFeature tree hash: {:?}\x1b[0m", + feature_tree.get_root_hash() + ); // ============================================================================ // Step 3: Create "hotfix" branch - another parallel development // ============================================================================ - println!("\n🚀 Creating hotfix branch from main..."); + println!("\n\x1b[1;32mCreating hotfix branch from main...\x1b[0m"); let storage_hotfix = InMemoryNodeStorage::<32>::default(); let mut hotfix_tree = ProllyTree::new(storage_hotfix, config); @@ -119,37 +136,42 @@ fn main() { hotfix_tree.insert(key.as_bytes().to_vec(), value.as_bytes().to_vec()); } - println!(" ✅ Hotfix branch created with urgent fixes"); - println!(" 🌳 Hotfix tree hash: {:?}", hotfix_tree.get_root_hash()); + println!(" \x1b[32mHotfix branch created with urgent fixes\x1b[0m"); + println!( + " \x1b[33mHotfix tree hash: {:?}\x1b[0m", + hotfix_tree.get_root_hash() + ); // ============================================================================ // Step 4: Perform Git-like diffs between branches // ============================================================================ - println!("\n🔍 Performing Git-like diffs...\n"); + println!("\n\x1b[1;34mPerforming Git-like diffs...\x1b[0m\n"); // Diff 1: main vs feature (like git diff main..feature) - println!("📊 Diff: main -> feature (shows what feature adds/changes)"); - println!(" Similar to: git diff main..feature"); + println!("\x1b[1;35mDiff: main -> feature (shows what feature adds/changes)\x1b[0m"); + println!(" \x1b[90mSimilar to: git diff main..feature\x1b[0m"); let main_to_feature_diff = main_tree.diff(&feature_tree); print_diff_summary(&main_to_feature_diff, "main", "feature"); // Diff 2: main vs hotfix (like git diff main..hotfix) - println!("\n📊 Diff: main -> hotfix (shows what hotfix adds/changes)"); - println!(" Similar to: git diff main..hotfix"); + println!("\n\x1b[1;35mDiff: main -> hotfix (shows what hotfix adds/changes)\x1b[0m"); + println!(" \x1b[90mSimilar to: git diff main..hotfix\x1b[0m"); let main_to_hotfix_diff = main_tree.diff(&hotfix_tree); print_diff_summary(&main_to_hotfix_diff, "main", "hotfix"); // Diff 3: feature vs hotfix (like git diff feature..hotfix) - println!("\n📊 Diff: feature -> hotfix (shows differences between parallel branches)"); - println!(" Similar to: git diff feature..hotfix"); + println!( + "\n\x1b[1;35mDiff: feature -> hotfix (shows differences between parallel branches)\x1b[0m" + ); + println!(" \x1b[90mSimilar to: git diff feature..hotfix\x1b[0m"); let feature_to_hotfix_diff = feature_tree.diff(&hotfix_tree); print_diff_summary(&feature_to_hotfix_diff, "feature", "hotfix"); // ============================================================================ // Step 5: Show detailed analysis // ============================================================================ - println!("\n📈 Detailed Diff Analysis:"); - println!("═══════════════════════════════════════"); + println!("\n\x1b[1;34mDetailed Diff Analysis:\x1b[0m"); + println!("\x1b[90m═══════════════════════════════════════\x1b[0m"); analyze_diff(&main_to_feature_diff, "Main → Feature"); analyze_diff(&main_to_hotfix_diff, "Main → Hotfix"); @@ -158,21 +180,21 @@ fn main() { // ============================================================================ // Summary // ============================================================================ - println!("\n🎯 Summary:"); - println!(" • Prolly Trees enable efficient git-like diffing"); - println!(" • Each branch maintains its own merkle tree structure"); - println!(" • Diffs show exact changes: additions, deletions, modifications"); - println!(" • Perfect for version control, distributed systems, and data synchronization"); - println!(" • Hash-based verification ensures data integrity across branches"); + println!("\n\x1b[1;36mSummary:\x1b[0m"); + println!(" \x1b[32m• Prolly Trees enable efficient git-like diffing\x1b[0m"); + println!(" \x1b[32m• Each branch maintains its own merkle tree structure\x1b[0m"); + println!(" \x1b[32m• Diffs show exact changes: additions, deletions, modifications\x1b[0m"); + println!(" \x1b[32m• Perfect for version control, distributed systems, and data synchronization\x1b[0m"); + println!(" \x1b[32m• Hash-based verification ensures data integrity across branches\x1b[0m"); } fn print_diff_summary(diffs: &[DiffResult], _from_branch: &str, _to_branch: &str) { let (added, removed, modified) = count_diff_types(diffs); - - println!(" 📈 Changes: +{} files, -{} files, ~{} files modified", added, removed, modified); - + + println!(" \x1b[33mChanges: \x1b[32m+{}\x1b[0m files, \x1b[31m-{}\x1b[0m files, \x1b[34m~{}\x1b[0m files modified", added, removed, modified); + if diffs.is_empty() { - println!(" ✨ No differences found - branches are identical"); + println!(" \x1b[32mNo differences found - branches are identical\x1b[0m"); } } @@ -193,8 +215,8 @@ fn count_diff_types(diffs: &[DiffResult]) -> (usize, usize, usize) { } fn analyze_diff(diffs: &[DiffResult], comparison: &str) { - println!("\n🔍 {}", comparison); - println!("───────────────────────────────"); + println!("\n\x1b[1;34m{}\x1b[0m", comparison); + println!("\x1b[90m───────────────────────────────\x1b[0m"); if diffs.is_empty() { println!(" No changes detected"); @@ -205,15 +227,15 @@ fn analyze_diff(diffs: &[DiffResult], comparison: &str) { match diff { DiffResult::Added(key, _value) => { let filename = String::from_utf8_lossy(key); - println!(" ➕ Added: {}", filename); + println!(" \x1b[32m+ Added: {}\x1b[0m", filename); } DiffResult::Removed(key, _value) => { let filename = String::from_utf8_lossy(key); - println!(" ➖ Removed: {}", filename); + println!(" \x1b[31m- Removed: {}\x1b[0m", filename); } DiffResult::Modified(key, _old_value, _new_value) => { let filename = String::from_utf8_lossy(key); - println!(" 🔄 Modified: {}", filename); + println!(" \x1b[34m~ Modified: {}\x1b[0m", filename); } } } diff --git a/examples/git_merge_example.rs b/examples/git_merge_example.rs index e5e1b48..4076c1c 100644 --- a/examples/git_merge_example.rs +++ b/examples/git_merge_example.rs @@ -33,9 +33,8 @@ enum MergeConflict { }, } - fn main() { - println!("🔀 Git-like Tree Merging Example 🔀\n"); + println!("\x1b[1;36mGit-like Tree Merging Example\x1b[0m\n"); // Create a shared configuration for consistency let config = TreeConfig { @@ -53,243 +52,277 @@ fn main() { // ============================================================================ // Scenario 1: Fast-forward merge (no conflicts) // ============================================================================ - println!("🚀 Scenario 1: Fast-forward merge"); - println!("═══════════════════════════════════"); - + println!("\x1b[1;32mScenario 1: Fast-forward merge\x1b[0m"); + println!("\x1b[90m═══════════════════════════════════\x1b[0m"); + let (main_tree_ff, feature_tree_ff) = create_fast_forward_scenario(config.clone()); - - println!(" 📊 Before merge:"); + + println!(" \x1b[33mBefore merge:\x1b[0m"); println!(" Main branch: {} files", main_tree_ff.size()); println!(" Feature branch: {} files", feature_tree_ff.size()); - + let merged_ff = perform_fast_forward_merge(&main_tree_ff, &feature_tree_ff); - println!(" ✅ Fast-forward merge completed!"); + println!(" \x1b[32mFast-forward merge completed!\x1b[0m"); println!(" Merged tree: {} files", merged_ff.size()); - println!(" Strategy: Fast-forward (no conflicts)\n"); + println!(" \x1b[90mStrategy: Fast-forward (no conflicts)\x1b[0m\n"); // ============================================================================ // Scenario 2: Three-way merge with automatic resolution // ============================================================================ - println!("🔄 Scenario 2: Three-way merge (auto-resolvable)"); - println!("═══════════════════════════════════════════════"); - + println!("\x1b[1;34mScenario 2: Three-way merge (auto-resolvable)\x1b[0m"); + println!("\x1b[90m═══════════════════════════════════════════════\x1b[0m"); + let (base_tree, branch_a, branch_b) = create_three_way_scenario(config.clone()); - - println!(" 📊 Before merge:"); + + println!(" \x1b[33mBefore merge:\x1b[0m"); println!(" Base (common ancestor): {} files", base_tree.size()); println!(" Branch A: {} files", branch_a.size()); println!(" Branch B: {} files", branch_b.size()); - + let merged_3way = perform_three_way_merge(&base_tree, &branch_a, &branch_b); - println!(" ✅ Three-way merge completed!"); + println!(" \x1b[32mThree-way merge completed!\x1b[0m"); println!(" Merged tree: {} files", merged_3way.size()); - println!(" Strategy: Three-way merge (auto-resolved)\n"); + println!(" \x1b[90mStrategy: Three-way merge (auto-resolved)\x1b[0m\n"); // ============================================================================ // Scenario 3: Merge with conflicts requiring resolution // ============================================================================ - println!("⚠️ Scenario 3: Merge with conflicts"); - println!("══════════════════════════════════════"); - + println!("\x1b[1;31mScenario 3: Merge with conflicts\x1b[0m"); + println!("\x1b[90m══════════════════════════════════════\x1b[0m"); + let (base_conflict, branch_conflict_a, branch_conflict_b) = create_conflict_scenario(config); - - println!(" 📊 Before merge:"); + + println!(" \x1b[33mBefore merge:\x1b[0m"); println!(" Base: {} files", base_conflict.size()); println!(" Branch A: {} files", branch_conflict_a.size()); println!(" Branch B: {} files", branch_conflict_b.size()); - - let (conflicts, merged_with_conflicts) = detect_and_resolve_conflicts( - &base_conflict, - &branch_conflict_a, - &branch_conflict_b - ); - + + let (conflicts, merged_with_conflicts) = + detect_and_resolve_conflicts(&base_conflict, &branch_conflict_a, &branch_conflict_b); + if !conflicts.is_empty() { - println!(" 🚨 Conflicts detected: {} conflicts", conflicts.len()); + println!( + " \x1b[31mConflicts detected: {} conflicts\x1b[0m", + conflicts.len() + ); for conflict in &conflicts { print_conflict(conflict); } - println!(" ✅ Conflicts resolved automatically!"); + println!(" \x1b[32mConflicts resolved automatically!\x1b[0m"); } - + println!(" Merged tree: {} files", merged_with_conflicts.size()); - println!(" Strategy: Conflict resolution\n"); + println!(" \x1b[90mStrategy: Conflict resolution\x1b[0m\n"); // ============================================================================ // Summary and Best Practices // ============================================================================ - println!("🎯 Summary & Best Practices:"); - println!("═══════════════════════════════"); - println!(" 1. 🚀 Fast-forward: When target branch is ahead of source"); - println!(" 2. 🔄 Three-way: When both branches have changes from common base"); - println!(" 3. ⚠️ Conflicts: When same data is modified differently"); - println!(" 4. 🔍 Always verify merge results with diff analysis"); - println!(" 5. 💾 Prolly Trees provide cryptographic verification of merges"); - println!(" 6. 🌳 Each merge creates a new tree with verifiable history"); - + println!("\x1b[1;36mSummary & Best Practices:\x1b[0m"); + println!("\x1b[90m═══════════════════════════════\x1b[0m"); + println!(" \x1b[32m1. Fast-forward: When target branch is ahead of source\x1b[0m"); + println!(" \x1b[34m2. Three-way: When both branches have changes from common base\x1b[0m"); + println!(" \x1b[31m3. Conflicts: When same data is modified differently\x1b[0m"); + println!(" \x1b[33m4. Always verify merge results with diff analysis\x1b[0m"); + println!(" \x1b[35m5. Prolly Trees provide cryptographic verification of merges\x1b[0m"); + println!(" \x1b[36m6. Each merge creates a new tree with verifiable history\x1b[0m"); + // Show the final verification - println!("\n🔍 Merge Verification:"); - println!(" Fast-forward hash: {:?}", merged_ff.get_root_hash()); - println!(" Three-way hash: {:?}", merged_3way.get_root_hash()); - println!(" Conflict-resolved hash: {:?}", merged_with_conflicts.get_root_hash()); + println!("\n\x1b[1;33mMerge Verification:\x1b[0m"); + println!( + " \x1b[32mFast-forward hash: {:?}\x1b[0m", + merged_ff.get_root_hash() + ); + println!( + " \x1b[34mThree-way hash: {:?}\x1b[0m", + merged_3way.get_root_hash() + ); + println!( + " \x1b[31mConflict-resolved hash: {:?}\x1b[0m", + merged_with_conflicts.get_root_hash() + ); } -fn create_fast_forward_scenario(config: TreeConfig<32>) -> (ProllyTree<32, InMemoryNodeStorage<32>>, ProllyTree<32, InMemoryNodeStorage<32>>) { +fn create_fast_forward_scenario( + config: TreeConfig<32>, +) -> ( + ProllyTree<32, InMemoryNodeStorage<32>>, + ProllyTree<32, InMemoryNodeStorage<32>>, +) { // Main branch (behind) let storage_main = InMemoryNodeStorage::<32>::default(); let mut main_tree = ProllyTree::new(storage_main, config.clone()); - + main_tree.insert(b"readme.md".to_vec(), b"# Project".to_vec()); main_tree.insert(b"src/lib.rs".to_vec(), b"// Library code".to_vec()); - + // Feature branch (ahead with additional commits) let storage_feature = InMemoryNodeStorage::<32>::default(); let mut feature_tree = ProllyTree::new(storage_feature, config); - + // Start with main's content feature_tree.insert(b"readme.md".to_vec(), b"# Project".to_vec()); feature_tree.insert(b"src/lib.rs".to_vec(), b"// Library code".to_vec()); - + // Add new features - feature_tree.insert(b"src/feature.rs".to_vec(), b"pub fn new_feature() {}".to_vec()); - feature_tree.insert(b"tests/test.rs".to_vec(), b"#[test] fn test_feature() {}".to_vec()); - + feature_tree.insert( + b"src/feature.rs".to_vec(), + b"pub fn new_feature() {}".to_vec(), + ); + feature_tree.insert( + b"tests/test.rs".to_vec(), + b"#[test] fn test_feature() {}".to_vec(), + ); + (main_tree, feature_tree) } -fn create_three_way_scenario(config: TreeConfig<32>) -> ( +fn create_three_way_scenario( + config: TreeConfig<32>, +) -> ( + ProllyTree<32, InMemoryNodeStorage<32>>, + ProllyTree<32, InMemoryNodeStorage<32>>, ProllyTree<32, InMemoryNodeStorage<32>>, - ProllyTree<32, InMemoryNodeStorage<32>>, - ProllyTree<32, InMemoryNodeStorage<32>> ) { // Base (common ancestor) let storage_base = InMemoryNodeStorage::<32>::default(); let mut base_tree = ProllyTree::new(storage_base, config.clone()); - + base_tree.insert(b"config.json".to_vec(), b"{\"version\": \"1.0\"}".to_vec()); base_tree.insert(b"main.rs".to_vec(), b"fn main() {}".to_vec()); - + // Branch A: adds documentation let storage_a = InMemoryNodeStorage::<32>::default(); let mut branch_a = ProllyTree::new(storage_a, config.clone()); - + branch_a.insert(b"config.json".to_vec(), b"{\"version\": \"1.0\"}".to_vec()); branch_a.insert(b"main.rs".to_vec(), b"fn main() {}".to_vec()); branch_a.insert(b"docs.md".to_vec(), b"# Documentation".to_vec()); - + // Branch B: adds tests let storage_b = InMemoryNodeStorage::<32>::default(); let mut branch_b = ProllyTree::new(storage_b, config); - + branch_b.insert(b"config.json".to_vec(), b"{\"version\": \"1.0\"}".to_vec()); branch_b.insert(b"main.rs".to_vec(), b"fn main() {}".to_vec()); branch_b.insert(b"test.rs".to_vec(), b"#[test] fn test() {}".to_vec()); - + (base_tree, branch_a, branch_b) } -fn create_conflict_scenario(config: TreeConfig<32>) -> ( +fn create_conflict_scenario( + config: TreeConfig<32>, +) -> ( + ProllyTree<32, InMemoryNodeStorage<32>>, + ProllyTree<32, InMemoryNodeStorage<32>>, ProllyTree<32, InMemoryNodeStorage<32>>, - ProllyTree<32, InMemoryNodeStorage<32>>, - ProllyTree<32, InMemoryNodeStorage<32>> ) { // Base let storage_base = InMemoryNodeStorage::<32>::default(); let mut base_tree = ProllyTree::new(storage_base, config.clone()); - + base_tree.insert(b"version.txt".to_vec(), b"1.0.0".to_vec()); base_tree.insert(b"shared.rs".to_vec(), b"// Shared code".to_vec()); - + // Branch A: updates version and modifies shared code let storage_a = InMemoryNodeStorage::<32>::default(); let mut branch_a = ProllyTree::new(storage_a, config.clone()); - - branch_a.insert(b"version.txt".to_vec(), b"1.1.0".to_vec()); // Conflict! - branch_a.insert(b"shared.rs".to_vec(), b"// Shared code - Feature A".to_vec()); // Conflict! + + branch_a.insert(b"version.txt".to_vec(), b"1.1.0".to_vec()); // Conflict! + branch_a.insert( + b"shared.rs".to_vec(), + b"// Shared code - Feature A".to_vec(), + ); // Conflict! branch_a.insert(b"feature_a.rs".to_vec(), b"// Feature A".to_vec()); - + // Branch B: updates version differently and modifies shared code differently let storage_b = InMemoryNodeStorage::<32>::default(); let mut branch_b = ProllyTree::new(storage_b, config); - - branch_b.insert(b"version.txt".to_vec(), b"1.0.1".to_vec()); // Conflict! - branch_b.insert(b"shared.rs".to_vec(), b"// Shared code - Feature B".to_vec()); // Conflict! + + branch_b.insert(b"version.txt".to_vec(), b"1.0.1".to_vec()); // Conflict! + branch_b.insert( + b"shared.rs".to_vec(), + b"// Shared code - Feature B".to_vec(), + ); // Conflict! branch_b.insert(b"feature_b.rs".to_vec(), b"// Feature B".to_vec()); - + (base_tree, branch_a, branch_b) } fn perform_fast_forward_merge( _main: &ProllyTree<32, InMemoryNodeStorage<32>>, - feature: &ProllyTree<32, InMemoryNodeStorage<32>> + feature: &ProllyTree<32, InMemoryNodeStorage<32>>, ) -> ProllyTree<32, InMemoryNodeStorage<32>> { // In a fast-forward merge, we simply adopt the feature branch // since it contains all of main's changes plus additional ones - + let storage = InMemoryNodeStorage::<32>::default(); let mut merged = ProllyTree::new(storage, TreeConfig::default()); - + // Copy all data from feature branch (which includes main + new changes) - collect_all_key_values(feature).into_iter().for_each(|(k, v)| { - merged.insert(k, v); - }); - + collect_all_key_values(feature) + .into_iter() + .for_each(|(k, v)| { + merged.insert(k, v); + }); + merged } fn perform_three_way_merge( - base: &ProllyTree<32, InMemoryNodeStorage<32>>, - branch_a: &ProllyTree<32, InMemoryNodeStorage<32>>, - branch_b: &ProllyTree<32, InMemoryNodeStorage<32>> + base: &ProllyTree<32, InMemoryNodeStorage<32>>, + branch_a: &ProllyTree<32, InMemoryNodeStorage<32>>, + branch_b: &ProllyTree<32, InMemoryNodeStorage<32>>, ) -> ProllyTree<32, InMemoryNodeStorage<32>> { let storage = InMemoryNodeStorage::<32>::default(); let mut merged = ProllyTree::new(storage, TreeConfig::default()); - + // Start with base for (key, value) in collect_all_key_values(base) { merged.insert(key, value); } - + // Apply changes from branch A let diff_a = base.diff(branch_a); apply_diff_to_tree(&mut merged, &diff_a); - + // Apply non-conflicting changes from branch B let diff_b = base.diff(branch_b); apply_diff_to_tree(&mut merged, &diff_b); - + merged } fn detect_and_resolve_conflicts( - base: &ProllyTree<32, InMemoryNodeStorage<32>>, - branch_a: &ProllyTree<32, InMemoryNodeStorage<32>>, - branch_b: &ProllyTree<32, InMemoryNodeStorage<32>> + base: &ProllyTree<32, InMemoryNodeStorage<32>>, + branch_a: &ProllyTree<32, InMemoryNodeStorage<32>>, + branch_b: &ProllyTree<32, InMemoryNodeStorage<32>>, ) -> (Vec, ProllyTree<32, InMemoryNodeStorage<32>>) { let mut conflicts = Vec::new(); - + let diff_a = base.diff(branch_a); let diff_b = base.diff(branch_b); - + // Group diffs by key to detect conflicts let mut changes_a: HashMap, &DiffResult> = HashMap::new(); let mut changes_b: HashMap, &DiffResult> = HashMap::new(); - + for diff in &diff_a { let key = get_diff_key(diff); changes_a.insert(key, diff); } - + for diff in &diff_b { let key = get_diff_key(diff); changes_b.insert(key, diff); } - + // Detect conflicts for (key, diff_a) in &changes_a { if let Some(diff_b) = changes_b.get(key) { // Both branches modified the same file - if let (DiffResult::Modified(_, _, new_a), DiffResult::Modified(_, _, new_b)) = (diff_a, diff_b) { + if let (DiffResult::Modified(_, _, new_a), DiffResult::Modified(_, _, new_b)) = + (diff_a, diff_b) + { conflicts.push(MergeConflict::BothModified { key: key.clone(), branch_a_value: new_a.clone(), @@ -298,16 +331,16 @@ fn detect_and_resolve_conflicts( } } } - + // Create merged tree with conflict resolution let storage = InMemoryNodeStorage::<32>::default(); let mut merged = ProllyTree::new(storage, TreeConfig::default()); - + // Start with base for (key, value) in collect_all_key_values(base) { merged.insert(key, value); } - + // Apply non-conflicting changes for diff in &diff_a { let key = get_diff_key(diff); @@ -315,42 +348,48 @@ fn detect_and_resolve_conflicts( apply_single_diff(&mut merged, diff); } } - + for diff in &diff_b { let key = get_diff_key(diff); if !conflicts.iter().any(|c| get_conflict_key(c) == &key) { apply_single_diff(&mut merged, diff); } } - + // Resolve conflicts (using a simple strategy: prefer branch A) for conflict in &conflicts { match conflict { - MergeConflict::BothModified { key, branch_a_value, .. } => { + MergeConflict::BothModified { + key, + branch_a_value, + .. + } => { merged.insert(key.clone(), branch_a_value.clone()); } } } - + (conflicts, merged) } -fn collect_all_key_values(tree: &ProllyTree<32, InMemoryNodeStorage<32>>) -> Vec<(Vec, Vec)> { +fn collect_all_key_values( + tree: &ProllyTree<32, InMemoryNodeStorage<32>>, +) -> Vec<(Vec, Vec)> { // This is a simplified implementation // In practice, you'd traverse the tree to collect all key-value pairs let mut result = Vec::new(); - + // For this example, we'll use the diff against an empty tree to get all entries let empty_storage = InMemoryNodeStorage::<32>::default(); let empty_tree = ProllyTree::new(empty_storage, TreeConfig::default()); - + let diff = empty_tree.diff(tree); for diff_result in diff { if let DiffResult::Added(key, value) = diff_result { result.push((key, value)); } } - + result } @@ -390,12 +429,23 @@ fn get_conflict_key(conflict: &MergeConflict) -> &Vec { fn print_conflict(conflict: &MergeConflict) { match conflict { - MergeConflict::BothModified { key, branch_a_value, branch_b_value, .. } => { + MergeConflict::BothModified { + key, + branch_a_value, + branch_b_value, + .. + } => { let filename = String::from_utf8_lossy(key); - println!(" ⚠️ Both modified: {}", filename); - println!(" Branch A: {}", String::from_utf8_lossy(branch_a_value)); - println!(" Branch B: {}", String::from_utf8_lossy(branch_b_value)); - println!(" Resolution: Using Branch A version"); + println!(" \x1b[31mBoth modified: {}\x1b[0m", filename); + println!( + " \x1b[33mBranch A: {}\x1b[0m", + String::from_utf8_lossy(branch_a_value) + ); + println!( + " \x1b[33mBranch B: {}\x1b[0m", + String::from_utf8_lossy(branch_b_value) + ); + println!(" \x1b[32mResolution: Using Branch A version\x1b[0m"); } } -} \ No newline at end of file +} From 7e8872ddcdedace14389fcbffa007ff244e62217 Mon Sep 17 00:00:00 2001 From: zhangfengcdt Date: Tue, 15 Jul 2025 21:19:54 -0700 Subject: [PATCH 3/3] edit readme file --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c23f6f8..cd0327b 100644 --- a/README.md +++ b/README.md @@ -191,9 +191,9 @@ The following features are for Prolly tree library for Version 0.2.0: - [X] Parquet/Avro block encoding and decoding The following features are for Prolly tree library for Version 0.2.1: -- [ ] tree diffing and merging -- [ ] show history of changes of the tree (git logs style) using `gitoxide` crate -- [ ] support build database index using Prolly Tree +- [X] tree diffing and merging examples +- [ ] show history of changes of the Prolly tree (git logs style) using `gitoxide` crate +- [ ] build database index using Prolly Tree The following features are for Prolly tree library for Version 0.2.2: - [ ] version-controlled databases