diff/
diff.rs

1extern crate radicle_surf;
2
3use std::{env::Args, str::FromStr, time::Instant};
4
5use radicle_git_ext::Oid;
6use radicle_surf::{diff::Diff, Repository};
7
8fn main() {
9    let options = get_options_or_exit();
10    let repo = init_repository_or_exit(&options.path_to_repo);
11    let head_oid = match options.head_revision {
12        HeadRevision::Head => repo.head().unwrap(),
13        HeadRevision::Commit(id) => Oid::from_str(&id).unwrap(),
14    };
15    let base_oid = Oid::from_str(&options.base_revision).unwrap();
16    let now = Instant::now();
17    let elapsed_nanos = now.elapsed().as_nanos();
18    let diff = repo.diff(base_oid, head_oid).unwrap();
19    print_diff_summary(&diff, elapsed_nanos);
20}
21
22fn get_options_or_exit() -> Options {
23    match Options::parse(std::env::args()) {
24        Ok(options) => options,
25        Err(message) => {
26            println!("{message}");
27            std::process::exit(1);
28        }
29    }
30}
31
32fn init_repository_or_exit(path_to_repo: &str) -> Repository {
33    match Repository::open(path_to_repo) {
34        Ok(repo) => repo,
35        Err(e) => {
36            println!("Failed to create repository: {e:?}");
37            std::process::exit(1);
38        }
39    }
40}
41
42fn print_diff_summary(diff: &Diff, elapsed_nanos: u128) {
43    diff.added().for_each(|created| {
44        println!("+++ {:?}", created.path);
45    });
46    diff.deleted().for_each(|deleted| {
47        println!("--- {:?}", deleted.path);
48    });
49    diff.modified().for_each(|modified| {
50        println!("mod {:?}", modified.path);
51    });
52
53    println!(
54        "created {} / deleted {} / modified {} / total {}",
55        diff.added().count(),
56        diff.deleted().count(),
57        diff.modified().count(),
58        diff.added().count() + diff.deleted().count() + diff.modified().count()
59    );
60    println!("diff took {elapsed_nanos} nanos ");
61}
62
63struct Options {
64    path_to_repo: String,
65    base_revision: String,
66    head_revision: HeadRevision,
67}
68
69enum HeadRevision {
70    Head,
71    Commit(String),
72}
73
74impl Options {
75    fn parse(args: Args) -> Result<Self, String> {
76        let args: Vec<String> = args.collect();
77        if args.len() != 4 {
78            return Err(format!(
79                "Usage: {} <path-to-repo> <base-revision> <head-revision>\n\
80                \tpath-to-repo: Path to the directory containing .git subdirectory\n\
81                \tbase-revision: Git commit ID of the base revision (one that will be considered less recent)\n\
82                \thead-revision: Git commit ID of the head revision (one that will be considered more recent) or 'HEAD' to use current git HEAD\n",
83                args[0]));
84        }
85
86        let path_to_repo = args[1].clone();
87        let base_revision = args[2].clone();
88        let head_revision = {
89            if args[3].eq_ignore_ascii_case("HEAD") {
90                HeadRevision::Head
91            } else {
92                HeadRevision::Commit(args[3].clone())
93            }
94        };
95
96        Ok(Options {
97            path_to_repo,
98            base_revision,
99            head_revision,
100        })
101    }
102}