Skip to main content

content_diff/
content_diff.rs

1//! Compare tree changes as content: `cargo run --example content_diff`.
2//! Uses a disposable repository; output is escaped byte data, not a unified patch.
3use std::sync::atomic::AtomicBool;
4
5use girt::content_diff::{BinaryMode, BlobContent, ContentDiff, DiffLimits, diff};
6use girt::{EntryMode, InitKind, ReadLimits, Repository, Tree, TreeEntry};
7
8fn main() -> Result<(), Box<dyn std::error::Error>> {
9    let root = tempfile::tempdir()?;
10    let repo = Repository::init(
11        girt::ObjectFormat::Sha1,
12        root.path().join("repo"),
13        InitKind::Bare,
14    )?;
15
16    let store = repo.loose_objects();
17    let old_blob = store.write_blob(b"heading\r\nold\n")?;
18    let new_blob = store.write_blob(b"heading\r\nnew\xff")?;
19
20    let old = store.write_tree(&Tree::new(
21        girt::ObjectFormat::Sha1,
22        vec![TreeEntry {
23            name: b"example.txt".to_vec(),
24            mode: EntryMode::Blob,
25            id: old_blob,
26        }],
27    )?)?;
28    let new = store.write_tree(&Tree::new(
29        girt::ObjectFormat::Sha1,
30        vec![TreeEntry {
31            name: b"example.txt".to_vec(),
32            mode: EntryMode::Executable,
33            id: new_blob,
34        }],
35    )?)?;
36
37    let objects = repo.objects()?;
38    let cancel = AtomicBool::new(false);
39    let limits = DiffLimits::default();
40
41    let changes = objects.compare_trees(Some(old), Some(new))?;
42    for change in changes {
43        println!(
44            "{}: {:?} -> {:?}",
45            escaped(&change.path),
46            change.old.map(|v| v.mode),
47            change.new.map(|v| v.mode)
48        );
49
50        // Gitlinks require a submodule policy; the blob adapter deliberately rejects them.
51        let content = BlobContent::read(
52            &objects,
53            &change,
54            ReadLimits::default(),
55            limits.max_input_bytes,
56            &cancel,
57        )?;
58
59        match diff(
60            &content.old,
61            &content.new,
62            BinaryMode::Auto,
63            limits,
64            &cancel,
65        )? {
66            ContentDiff::Unchanged => println!("  unchanged content (check modes/existence above)"),
67            ContentDiff::Binary { old, new } => {
68                println!("  binary: {} -> {} bytes", old.len(), new.len())
69            }
70            ContentDiff::Text(text) => {
71                for edit in text.edits() {
72                    println!(
73                        "  old lines {:?} -> new lines {:?} (zero-based)",
74                        edit.old_lines, edit.new_lines
75                    );
76                    println!("  - {}", escaped(&text.old_bytes()[edit.old_bytes.clone()]));
77                    println!("  + {}", escaped(&text.new_bytes()[edit.new_bytes.clone()]));
78                }
79            }
80            _ => return Err("unsupported content diff kind".into()),
81        }
82    }
83
84    Ok(())
85}
86
87fn escaped(bytes: &[u8]) -> String {
88    bytes
89        .iter()
90        .flat_map(|b| std::ascii::escape_default(*b))
91        .map(char::from)
92        .collect()
93}