Skip to main content

compare_trees/
compare_trees.rs

1//! Compare explicit tree IDs: `cargo run --example compare_trees -- REPO OLD NEW`.
2//! Use `-` for either empty side. Without arguments, compare two disposable sample trees.
3use std::path::PathBuf;
4use std::str::FromStr;
5
6use clap::Parser;
7use girt::display::quote_path;
8use girt::{EntryMode, InitKind, ObjectId, Repository, Tree, TreeEntry, TreeValue};
9
10/// One side of a comparison: a tree ID, or `-` for an empty side.
11#[derive(Clone, Copy, Debug)]
12struct Side(Option<ObjectId>);
13
14impl FromStr for Side {
15    type Err = girt::ParseObjectIdError;
16
17    fn from_str(text: &str) -> Result<Self, Self::Err> {
18        if text == "-" {
19            return Ok(Self(None));
20        }
21        let id = text.parse()?;
22        Ok(Self(Some(id)))
23    }
24}
25
26#[derive(Parser)]
27#[command(about, long_about = None)]
28struct Args {
29    /// Repository to open. Omit all arguments to compare two disposable sample trees.
30    #[arg(requires = "old", requires = "new")]
31    repo: Option<PathBuf>,
32    /// Old tree ID, or `-` for an empty side.
33    old: Option<Side>,
34    /// New tree ID, or `-` for an empty side.
35    new: Option<Side>,
36}
37
38fn main() -> girt::Result<()> {
39    let args = Args::parse();
40    let temporary;
41    let (repo, old, new) =
42        if let (Some(path), Some(old), Some(new)) = (args.repo, args.old, args.new) {
43            (Repository::open(path)?, old.0, new.0)
44        } else {
45            temporary = tempfile::tempdir()?;
46            let repo = Repository::init(
47                girt::ObjectFormat::Sha1,
48                temporary.path().join("sample"),
49                InitKind::Bare,
50            )?;
51
52            let store = repo.loose_objects();
53            let old_blob = store.write_blob(b"hello\n")?;
54            let new_blob = store.write_blob(b"hello, trees\n")?;
55
56            let old = store.write_tree(&Tree::new(
57                girt::ObjectFormat::Sha1,
58                vec![TreeEntry {
59                    name: b"hello.txt".to_vec(),
60                    mode: EntryMode::Blob,
61                    id: old_blob,
62                }],
63            )?)?;
64            let new = store.write_tree(&Tree::new(
65                girt::ObjectFormat::Sha1,
66                vec![TreeEntry {
67                    name: b"hello.txt".to_vec(),
68                    mode: EntryMode::Executable,
69                    id: new_blob,
70                }],
71            )?)?;
72
73            (repo, Some(old), Some(new))
74        };
75
76    let objects = repo.objects()?;
77    let changes = objects.compare_trees(old, new)?;
78
79    for change in changes {
80        // Git's quoting keeps non-UTF-8 names and control bytes distinguishable on a terminal.
81        println!(
82            "{} {}: {} -> {}",
83            change.status().letter(),
84            quote_path(&change.path),
85            describe(change.old),
86            describe(change.new)
87        );
88    }
89
90    Ok(())
91}
92
93fn describe(value: Option<TreeValue>) -> String {
94    match value {
95        Some(value) => format!("{} {}", value.mode.octal(), value.id),
96        None => "absent".into(),
97    }
98}